UNPKG

@interaktiv/mibuilder-core

Version:

Core libraries to interact with MiBuilder projects.

349 lines (285 loc) 9.42 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigAggregator = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _dxl = require("@interaktiv/dxl"); var _types = require("@interaktiv/types"); var _mibuilderError = require("../mibuilder-error"); var _config = require("./config"); const env = new _dxl.Env(process.env, 'MIBUILDER'); const propertyToEnvName = property => env.getScopedEnvKey(`${(0, _dxl.toSnakeCase)(property).toUpperCase()}`); /** * Aggregate global and local project config files, as well as environment * variables for `mibuilder-config.json`. The resolution happens in the * following bottom-up order: * * 1. Environment variables (`MIBUILDER_LOG_LEVEL`) * 1. Workspace settings (`<workspace-root>/.mibuilder/mibuilder-config.json`) * 1. Global settings (`$HOME/.mibuilder/mibuilder-config.json`) * * Use {@link ConfigAggregator.create} to instantiate the aggregator. * * ``` * const aggregator = await ConfigAggregator.create(); * console.log(aggregator.getPropertyValue('defaultusername')); * ``` */ class ConfigAggregator extends _dxl.AsyncOptionalCreatable { /** * **Do not directly construct instances of this class -- use {@link ConfigAggregator.create} instead.** * * @ignore * @param {Object} [options={}] The options */ constructor(options) { super(options || {}); } /** * Initialize this instances async dependencies. */ async init() { await this.loadProperties(); } /** * Loads all the properties and aggregates them according to location. */ async loadProperties() { // Don't throw an project error with the aggregator, since it should // resolve to global if there is no project. try { this.setLocalConfig(await _config.Config.create(_config.Config.getDefaultOptions(false))); } catch (err) { if (err.name !== 'InvalidProjectWorkspace') { throw err; } } this.setGlobalConfig(await _config.Config.create(_config.Config.getDefaultOptions(true))); this.setAllowedProperties(_config.Config.getAllowedProperties()); this.setEnvVars( // Group allowed props by group (0, _types.definiteEntriesOf)((0, _dxl.groupBy)(this.getAllowedProperties(), 'group')) // Construct env var name by prefixing group .map(([group, properties]) => properties.map(prop => (0, _extends2.default)({}, prop, { envKey: `${group}_${prop.key}` }))) // Flatten props back down to 1 level array .reduce((flatten, current) => [...flatten, ...current], []) // Fetch scoped env var values and reduce to final env config .reduce((vars, prop) => { const val = env.parseEnv(propertyToEnvName(prop.envKey)); if (val == null) return vars; const group = vars[prop.group] || {}; return (0, _extends2.default)({}, vars, { [prop.group]: (0, _extends2.default)({}, group, { [prop.key]: val }) }); }, {})); // Global config must be read first so it is on the left hand of the // object assign and is overwritten by the local config. await this.globalConfig.read(); const configs = [this.globalConfig.toObject()]; // We might not be in a project workspace if (this.localConfig) { await this.localConfig.read(); configs.push(this.localConfig.toObject()); } configs.push(this.envVars); const reduced = configs.filter(_types.isJsonMap).reduce((acc, el) => (0, _dxl.merge)(acc, el), {}); this.setConfig(reduced); } /** * Get a resolved config property. * * **Throws** *{@link MiBuilderError}{ name: 'UnknownConfigKey' }* An attempt to get a property that's not supported. * * @param {String} path The path to the property. * @return {*} The value for the given key */ getPropertyValue(path) { const { group, key } = _config.Config.getKeyAndGroupFromPath(path); if (this.allowedProperties.find(prop => prop.group === group && prop.key === key) == null) { throw new _mibuilderError.MiBuilderError(`Unknown config key: ${path}`, 'UnknownConfigKey'); } return (0, _types.get)(this.config, path); } /** * Get a resolved config property. * * @param {String} path The path to the property * @return {Object} The info for the config property */ getInfo(path) { const location = this.getLocation(path); return { key: path, location, value: this.getPropertyValue(path), path: this.getPath(path), isLocal: () => location === 'Local', isGlobal: () => location === 'Global', isEnvVar: () => location === 'Environment' }; } /** * Gets a resolved config property location. * * For example, `getLocation('logLevel')` will return: * 1. `Location.ENVIRONMENT` if resolved to an environment variable. * 1. `Location.LOCAL` if resolved to local project config. * 1. `Location.GLOBAL` if resolved to the global config. * * @param {String} path The key of the property. * @return {String} The location for the */ getLocation(path) { // Environment if (this.getEnvVars().get(path) != null) return 'Environment'; const { group, key } = _config.Config.getKeyAndGroupFromPath(path); // Local if (this.getLocalConfig() && this.getLocalConfig().getInGroup(key, group)) { return 'Local'; } // Global if (this.getGlobalConfig() && this.getGlobalConfig().getInGroup(key, group)) { return 'Global'; } return undefined; } /** * Get a resolved file path or environment variable name of the property. * * For example, `getPath('logLevel')` will return: * 1. `$MIBUILDER_LOG_LEVEL` if resolved to an environment variable. * 1. `./.mibuilder/mibuilder-config.json` if resolved to the local config. * 1. `~/.mibuilder/mibuilder-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 {String} path The key of the property * @return {String} The file path or env var to the prop */ getPath(path) { if ((0, _types.get)(this.envVars, path) != null) { return `$${propertyToEnvName(path)}`; } if ((0, _types.get)(this.getLocalConfig(), `contents[${path}]`) != null) { return this.getLocalConfig().getPath(); } if ((0, _types.get)(this.getGlobalConfig(), `contents[${path}]`) != null) { return this.getGlobalConfig().getPath(); } return undefined; } /** * Get all resolved config property keys, values, locations, and paths. * * ``` * > console.log(aggregator.getConfigInfo()); * [ * { key: 'logLevel', val: 'INFO', location: 'Environment', path: '$MIBUILDER_LOG_LEVEL'} * { key: 'defaultusername', val: '<username>', location: 'Local', path: './.mibuilder/mibuilder-config.json'} * ] * ``` * * @return {Object[]} The config info */ getConfigInfo() { const infos = (0, _types.keysOf)((0, _dxl.walkObject)(this.getConfig())).map(key => this.getInfo(key)).filter(info => !!info); return (0, _dxl.sortBy)(infos, 'key'); } /** * Get the local project config instance. * * @return {Object<Config>} The local config */ getLocalConfig() { return this.localConfig; } /** * Get the global config instance. * * @return {Object<Config>} The global config */ getGlobalConfig() { return this.globalConfig; } /** * Get the resolved config object from the local, global and environment * config instances. * * @return {Object} this.config */ getConfig() { return this.config; } /** * Get the config properties that are environment variables. * * @return {Map} The config props that are env vars */ getEnvVars() { return new Map((0, _types.definiteEntriesOf)((0, _dxl.walkObject)(this.envVars))); } /** * Re-read all property configurations from disk. * * @return {Object<ConfigAggregator>} this */ async reload() { await this.loadProperties(); return this; } /** * Set the resolved config object. * * @param {Object} newConfig The config object to set. */ setConfig(newConfig) { this.config = newConfig; } /** * Set the local config object. * * @param {Object} newConfig The config object value to set. */ setLocalConfig(newConfig) { this.localConfig = newConfig; } /** * Set the global config object. * * @param {Object} newConfig The config object value to set. */ setGlobalConfig(newConfig) { this.globalConfig = newConfig; } /** * Sets the env variables. * * @param {Object} envVars The env variables to set. */ setEnvVars(envVars) { this.envVars = envVars; } /** * Get the allowed properties. * * @return {Object} The allowed properties */ getAllowedProperties() { return this.allowedProperties; } /** * Set the allowed properties. * * @param {Object} properties The properties to set. */ setAllowedProperties(properties) { this.allowedProperties = properties; } } exports.ConfigAggregator = ConfigAggregator;