@interaktiv/mibuilder-core
Version:
Core libraries to interact with MiBuilder projects.
349 lines (285 loc) • 9.42 kB
JavaScript
"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;