UNPKG

@salesforce/core

Version:

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

356 lines 17.7 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.ConfigAggregator = void 0; const kit_1 = require("@salesforce/kit"); const ts_types_1 = require("@salesforce/ts-types"); const messages_1 = require("../messages"); const lifecycleEvents_1 = require("../lifecycleEvents"); const envVars_1 = require("./envVars"); const config_1 = require("./config"); ; const messages = new messages_1.Messages('@salesforce/core', 'config', new Map([["unknownConfigKey", "Unknown config name: %s."], ["deprecatedConfigKey", "Deprecated config name: %s. Please use %s instead."], ["invalidWrite", "The writeSync method is not allowed on SfdxConfig. Use the async write method instead."], ["invalidConfigValue", "Invalid config value: %s."], ["invalidInstanceUrl", "Specify a valid Salesforce instance URL."], ["invalidApiVersion", "Specify a valid Salesforce API version, for example, 42.0."], ["invalidCustomOrgMetadataTemplates", "Specify a valid repository URL or directory for the custom org metadata templates."], ["invalidIsvDebuggerSid", "Specify a valid Debugger SID."], ["invalidIsvDebuggerUrl", "Specify a valid Debugger URL."], ["invalidNumberConfigValue", "Specify a valid positive integer, for example, 150000."], ["invalidBooleanConfigValue", "The config value can only be set to true or false."], ["invalidProjectWorkspace", "This directory does not contain a valid Salesforce DX project."], ["schemaValidationError", "The config file \"%s\" is not schema valid.\nDue to: %s"], ["schemaValidationError.actions", ["Fix the invalid entries at %s."]], ["missingDefaultPath", "In sfdx-project.json, be sure to specify which package directory (path) is the default. Example: `[{ \"path\": \"packageDirectory1\", \"default\": true }, { \"path\": \"packageDirectory2\" }]`"], ["missingPackageDirectory", "The path \"%s\", specified in sfdx-project.json, does not exist. Be sure this directory is included in your project root."], ["invalidPackageDirectory", "The path \"%s\", specified in sfdx-project.json, must be indicated as a relative path to the project root."], ["multipleDefaultPaths", "In sfdx-project.json, indicate only one package directory (path) as the default."], ["singleNonDefaultPackage", "The sfdx-project.json file must include one, and only one, default package directory (path). Because your sfdx-project.json file contains only one package directory, it must be the default. Remove the `\"default\": false` key and try again."], ["target-org", "Username or alias of the org that all commands run against by default. (sf only)"], ["target-dev-hub", "Username or alias of your default Dev Hub org. (sf only)"], ["defaultUsername", "Username or alias of the org that all commands run against by default. (sfdx only)"], ["defaultDevHubUsername", "Username or alias of your default Dev Hub org. (sfdx only)"], ["isvDebuggerSid", "ISV debugger SID (sfdx only)"], ["isvDebuggerUrl", "ISV debugger URL (sfdx only)"], ["org-isv-debugger-sid", "ISV debugger SID."], ["org-isv-debugger-url", "ISV debugger URL."], ["apiVersion", "API version of your project. Default: API version of your Dev Hub org. (sfdx only)"], ["org-api-version", "API version of your project. Default: API version of your Dev Hub org."], ["disableTelemetry", "Disables the collection of usage and user environment information, etc. Default: false. (sfdx only)"], ["disable-telemetry", "Disables the collection of usage and user environment information, etc. Default: false."], ["maxQueryLimit", "Maximum number of Salesforce records returned by a CLI command. Default: 10,000. (sfdx only)"], ["org-max-query-limit", "Maximum number of Salesforce records returned by a CLI command. Default: 10,000."], ["restDeploy", "Whether deployments use the Metadata REST API (true) or SOAP API (false, default value). (sfdx only)"], ["instanceUrl", "URL of the Salesforce instance hosting your org. Default: https://login.salesforce.com. (sfdx only)"], ["org-instance-url", "URL of the Salesforce instance hosting your org. Default: https://login.salesforce.com."], ["customOrgMetadataTemplates", "A valid repository URL or directory for the custom org metadata templates."], ["org-custom-metadata-templates", "A valid repository URL or directory for the custom org metadata templates."], ["org-capitalize-record-types", "Whether record types are capitalized on scratch org creation."], ["invalidId", "The given id %s is not a valid 15 or 18 character Salesforce ID."]])); /** * Aggregate global and local project config files, as well as environment variables for * `config.json`. The resolution happens in the following bottom-up order: * * 1. Environment variables (`SF_LOG_LEVEL`) * 1. Workspace settings (`<workspace-root>/.sf/config.json`) * 1. Global settings (`$HOME/.sf/config.json`) * * Use {@link ConfigAggregator.create} to instantiate the aggregator. * * ``` * const aggregator = await ConfigAggregator.create(); * console.log(aggregator.getPropertyValue('target-org')); * ``` */ class ConfigAggregator extends kit_1.AsyncOptionalCreatable { static instance; static encrypted = true; // Initialized in loadProperties allowedProperties; localConfig; globalConfig; envVars = {}; /** * **Do not directly construct instances of this class -- use {@link ConfigAggregator.create} instead.** * * @ignore */ constructor(options) { super(options ?? {}); // Don't throw an project error with the aggregator, since it should resolve to global if // there is no project. try { this.localConfig = new config_1.Config(config_1.Config.getDefaultOptions(false)); } catch (err) { if (err.name !== 'InvalidProjectWorkspaceError') { throw err; } } this.globalConfig = new config_1.Config(config_1.Config.getDefaultOptions(true)); this.setAllowedProperties(config_1.Config.getAllowedProperties()); } get config() { return this.resolveProperties(this.globalConfig.getContents(), this.localConfig?.getContents()); } // Use typing from AsyncOptionalCreatable to support extending ConfigAggregator. // We really don't want ConfigAggregator extended but typescript doesn't support a final. static async create(options) { let config = ConfigAggregator.instance; if (!config) { config = ConfigAggregator.instance = new this(options); await config.init(); } if (ConfigAggregator.encrypted) { await config.loadProperties(); } if (options?.customConfigMeta) { config_1.Config.addAllowedProperties(options.customConfigMeta); } return ConfigAggregator.instance; } /** * Get the info for a given key. If the ConfigAggregator was not asynchronously created OR * the {@link ConfigAggregator.reload} was not called, the config value may be encrypted. * * @param key The config key. */ static getValue(key) { return this.getInstance().getInfo(key); } /** * Get the static ConfigAggregator instance. If one doesn't exist, one will be created with * the **encrypted** config values. Encrypted config values need to be resolved * asynchronously by calling {@link ConfigAggregator.reload} */ // Use typing from AsyncOptionalCreatable to support extending ConfigAggregator. // We really don't want ConfigAggregator extended but typescript doesn't support a final. static getInstance() { if (!ConfigAggregator.instance) { ConfigAggregator.instance = new this(); ConfigAggregator.instance.loadPropertiesSync(); } return ConfigAggregator.instance; } /** * Initialize this instances async dependencies. */ async init() { await this.loadProperties(); } /** * Get a resolved config property. * If you use a deprecated property, a warning will be emitted and it will attempt to resolve the new property's value * * **Throws** *{@link SfError}{ name: 'UnknownConfigKeyError' }* An attempt to get a property that's not supported. * * @param key The key of the property. */ getPropertyValue(key) { const match = this.getAllowedProperties().find((element) => key === element.key); if (match?.deprecated && match.newKey) { void lifecycleEvents_1.Lifecycle.getInstance().emitWarning(messages.getMessage('deprecatedConfigKey', [key, match.newKey])); const newKeyMatch = this.getAllowedProperties().find((element) => match.newKey === element.key); if (newKeyMatch) { return this.getConfig()[newKeyMatch.key] ?? this.getConfig()[match.key]; } } if (this.getAllowedProperties().some((element) => key === element.key || key === element.newKey)) { return this.getConfig()[key]; } else { throw messages.createError('unknownConfigKey', [key]); } } /** * Get a resolved config property meta. * If the property is deprecated, it will return the new key's meta, if it exists, with a deprecation warning * * **Throws** *{@link SfError}{ name: 'UnknownConfigKeyError' }* An attempt to get a property that's not supported. * * @param key The key of the property. */ getPropertyMeta(key) { const match = this.getAllowedProperties().find((element) => key === element.key); if (match) { if (match.deprecated && match.newKey) { void lifecycleEvents_1.Lifecycle.getInstance().emitWarning(messages.getMessage('deprecatedConfigKey', [key, match.newKey])); const newKeyMatch = this.getAllowedProperties().find((element) => key === element.newKey); if (newKeyMatch) { return newKeyMatch ?? match; } } return match; } const matchFromNewKey = this.getAllowedProperties().find((element) => key === element.newKey); if (matchFromNewKey) { return matchFromNewKey; } throw messages.createError('unknownConfigKey', [key]); } /** * Get a resolved config property. * If a property is deprecated, it will try to use the the new key, if there is a config there. * * @param key The key of the property. * @param throwOnDeprecation True, if you want an error throw when reading a deprecated config */ getInfo(key, throwOnDeprecation = false) { const meta = this.getPropertyMeta(key); if (meta.deprecated && meta.newKey) { if (throwOnDeprecation) { throw messages.createError('deprecatedConfigKey', [key, meta.newKey]); } // we don't need to emit a deprecatedConfigKey warning here because getPropertyMeta does that } const location = meta.newKey ? this.getLocation(meta.newKey) : this.getLocation(key); return { key: meta.newKey ?? key, location, value: this.getPropertyValue(meta.newKey ?? key), path: this.getPath(meta.newKey ?? key), isLocal: () => location === "Local" /* ConfigAggregator.Location.LOCAL */, isGlobal: () => location === "Global" /* ConfigAggregator.Location.GLOBAL */, isEnvVar: () => location === "Environment" /* ConfigAggregator.Location.ENVIRONMENT */, deprecated: meta.deprecated ?? false, }; } /** * Gets a resolved config property location. * * For example, `getLocation('logLevel')` will return: * 1. `Location.GLOBAL` if resolved to an environment variable. * 1. `Location.LOCAL` if resolved to local project config. * 1. `Location.ENVIRONMENT` if resolved to the global config. * * @param key The key of the property. */ getLocation(key) { // envs populate old and new automatically if (this.envVars[key] != null) { return "Environment" /* ConfigAggregator.Location.ENVIRONMENT */; } if (this.localConfig?.get(key)) { return "Local" /* ConfigAggregator.Location.LOCAL */; } if (this.globalConfig?.get(key)) { return "Global" /* ConfigAggregator.Location.GLOBAL */; } } /** * Get a resolved file path or environment variable name of the property. * * For example, `getPath('logLevel')` will return: * 1. `$SF_LOG_LEVEL` if resolved to an environment variable. * 1. `./.sf/config.json` if resolved to the local config. * 1. `~/.sf/config.json` if resolved to the global config. * 1. `undefined`, if not resolved. * * **Note:** that the path returned may be the absolute path instead of * relative paths such as `./` and `~/`. * * @param key The key of the property. */ getPath(key) { // TODO: make EnvVars always prefer the "new" regardless of CLI if (this.envVars[key] != null) { return `$${envVars_1.EnvVars.propertyToEnvName(key)}`; } if (this.localConfig?.getContents()[key] != null) { return this.localConfig.getPath(); } if (this.globalConfig.getContents()[key] != null) { return this.globalConfig.getPath(); } } /** * Get all resolved config property keys, values, locations, and paths. * * ``` * > console.log(aggregator.getConfigInfo()); * [ * { key: 'logLevel', val: 'INFO', location: 'Environment', path: '$SF_LOG_LEVEL'} * { key: 'target-org', val: '<username>', location: 'Local', path: './.sf/config.json'} * ] * ``` */ getConfigInfo() { const infos = Object.keys(this.getConfig()) .filter((key) => this.getAllowedProperties().some((element) => key === element.key || key === element.newKey)) .map((key) => this.getInfo(key)) .filter((info) => !!info); return (0, kit_1.sortBy)(infos, 'key'); } /** * Get the local project config instance. */ getLocalConfig() { return this.localConfig; } /** * Get the global config instance. */ getGlobalConfig() { return this.globalConfig; } /** * Get the resolved config object from the local, global and environment config instances. */ getConfig() { return this.config; } async unsetByValue(key) { const hasLocalWrites = this.localConfig ?.getKeysByValue(key) .map((k) => this.localConfig?.unset(k)) .some(Boolean); if (hasLocalWrites) await this.localConfig?.write(); const hasGlobalWrites = this.globalConfig ?.getKeysByValue(key) .map((k) => this.globalConfig?.unset(k)) .some(Boolean); if (hasGlobalWrites) await this.globalConfig?.write(); } /** * Get the config properties that are environment variables. */ getEnvVars() { return this.envVars; } /** * Re-read all property configurations from disk. */ async reload() { await this.loadProperties(); return this; } /** * Add an allowed config property. */ addAllowedProperties(configMetas) { if ((0, ts_types_1.isArray)(configMetas)) { this.allowedProperties.push(...configMetas); } else { this.allowedProperties.push(configMetas); } } /** * Set the allowed properties. * * @param properties The properties to set. */ setAllowedProperties(properties) { this.allowedProperties = properties; } /** * Get the allowed properties. */ getAllowedProperties() { return this.allowedProperties; } /** * Loads all the properties and aggregates them according to location. */ async loadProperties() { this.resolveProperties(await this.globalConfig.read(), this.localConfig && (await this.localConfig.read())); ConfigAggregator.encrypted = false; } /** * Loads all the properties and aggregates them according to location. */ loadPropertiesSync() { this.resolveProperties(this.globalConfig.readSync(), this.localConfig?.readSync()); } resolveProperties(globalConfig, localConfig) { const envVars = new envVars_1.EnvVars(); for (const property of this.getAllowedProperties()) { const key = property.newKey ? property.newKey : property.key; const value = envVars.getPropertyFromEnv(property.key); if (value) this.envVars[key] = value; } // Global config must be read first so it is on the left hand of the // object assign and is overwritten by the local config. const configs = [globalConfig]; // We might not be in a project workspace if (localConfig) { configs.push(localConfig); } configs.push(this.envVars); const json = {}; return configs.filter(ts_types_1.isJsonMap).reduce((acc, el) => (0, kit_1.merge)(acc, el), json); } } exports.ConfigAggregator = ConfigAggregator; //# sourceMappingURL=configAggregator.js.map