Skip to content
This repository was archived by the owner on Dec 20, 2023. It is now read-only.

Commit f4ae05e

Browse files
author
Nathan Lincoln
authoredJul 14, 2021
fix: select env after merging all files together (#47)
* fix: select env after merging all files together * refactor: Use an internal ConfigLocation concept to represent override priority * Actually commit the ConfigLocation file 🤦 * Some nice code comments * Refactors createLoaders to take advantage of only having one location
1 parent d1150f0 commit f4ae05e

File tree

4 files changed

+178
-62
lines changed

4 files changed

+178
-62
lines changed
 

‎lib/ConfigLocation.js

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/**
2+
* @projectName gofigure
3+
* @github http://github.com/C2FO/gofigure
4+
* @header [../README.md]
5+
* @includeDoc [Change Log] ../History.md
6+
*/
7+
8+
const _ = require('lodash');
9+
const PatternEventEmitter = require('./PatternEventEmitter');
10+
const loader = require('./loader');
11+
const processor = require('./processor');
12+
13+
class ConfigLocation extends PatternEventEmitter {
14+
/**
15+
* Aggregate class for a single entry in the `locations` array.
16+
*
17+
* Locations will load all the config files specified within their list
18+
* and then merge them together.
19+
*
20+
* @param {{ file: string }} locationName the location that this instance should own
21+
* @param opts @see ConfigLoader
22+
*/
23+
constructor(locationName, opts) {
24+
super({});
25+
if (!locationName) {
26+
throw new Error('A locationName is required');
27+
}
28+
const options = opts || {};
29+
this.environment = options.environment || process.env.NODE_ENV || null;
30+
this.nodeType = options.nodeType || process.env.NODE_TYPE || null;
31+
this.environmentVariables = options.environmentVariables || process.env || {};
32+
this.defaultEnvironment = options.defaultEnvironment || '*';
33+
this.config = {};
34+
this.monitor = !!options.monitor;
35+
this.locationName = locationName;
36+
this.loaded = false;
37+
}
38+
39+
/**
40+
* Stops monitoring confgurations for changes.
41+
*
42+
* @return {Config} this config object.
43+
*/
44+
stop() {
45+
this.__loaders.forEach((l) => l.unWatch());
46+
return this;
47+
}
48+
49+
/**
50+
* Asynchronously loads configs.
51+
*
52+
* @example
53+
* configLoader.load().then((config) => {
54+
* // use the config.
55+
* })
56+
*
57+
* @return {Promise<any>|Promise<any[] | never>} resolves with the merged configuration from all locations.s
58+
*/
59+
load() {
60+
if (this.__loaded) {
61+
return Promise.resolve(this.config);
62+
}
63+
return loader.createLoadersAsync(this.locationName, { monitor: this.monitor }).then((loaders) => {
64+
this.__loaders = loaders;
65+
const loads = loaders.map((l) => l.load());
66+
return Promise.all(loads).then((configs) => this.__postLoad(configs));
67+
});
68+
}
69+
70+
/**
71+
* Synchronously loads configurations.
72+
*
73+
* @example
74+
* const config = configLoader.loadSync()
75+
* //use your config.
76+
*
77+
* @return {Object} the merged configuration from all locations.
78+
*/
79+
loadSync() {
80+
if (this.__loaded) {
81+
return this.config;
82+
}
83+
this.__loaders = loader.createLoadersSync(this.locationName, { monitor: this.monitor });
84+
return this.__postLoad(this.__loaders.map((l) => l.loadSync()));
85+
}
86+
87+
__mergeConfigs(configs) {
88+
return processor(_.cloneDeep(this.config), configs, {
89+
environment: this.environment,
90+
defaultEnvironment: this.defaultEnvironment,
91+
nodeType: this.nodeType,
92+
eventEmitter: this,
93+
environmentVariables: this.environmentVariables,
94+
});
95+
}
96+
97+
/**
98+
* Merges configs and starts monitoring of configs if `monitor` is true.
99+
* @param configs {Array<Object>} an array of configurations to merge into a single object.
100+
* @return {Object} the merged config
101+
* @private
102+
*/
103+
__postLoad(configs) {
104+
this.config = this.__mergeConfigs(configs);
105+
this.__listenToChanges();
106+
this.__loaded = true;
107+
return this.config;
108+
}
109+
110+
__listenToChanges() {
111+
this.__loaders.forEach((l) =>
112+
l.on('change', (path, config) => {
113+
/**
114+
* Whenever one of our loaders reports that their file was changed,
115+
* we forward that event onward.
116+
*
117+
* The `config` object here is just the raw contents of the file. Our "parent"
118+
* listener expects us to forward them the resolved config, so we process it before
119+
* forwarding it.
120+
*/
121+
this.config = this.__mergeConfigs([config]);
122+
123+
this.emit('change', this.config);
124+
}),
125+
);
126+
}
127+
}
128+
129+
module.exports = ConfigLocation;

‎lib/index.js

+25-25
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
const _ = require('lodash');
99
const PatternEventEmitter = require('./PatternEventEmitter');
10-
const loader = require('./loader');
11-
const processor = require('./processor');
10+
const ConfigLocation = require('./ConfigLocation');
11+
const merger = require('./processor/merger');
1212

1313
class ConfigLoader extends PatternEventEmitter {
1414
/**
@@ -28,15 +28,15 @@ class ConfigLoader extends PatternEventEmitter {
2828
if (!loaderConfig) {
2929
throw new Error('A loader configuration is required');
3030
}
31-
const options = opts || {};
32-
this.environment = options.environment || process.env.NODE_ENV || null;
33-
this.nodeType = options.nodeType || process.env.NODE_TYPE || null;
34-
this.environmentVariables = options.environmentVariables || process.env || {};
35-
this.defaultEnvironment = options.defaultEnvironment || '*';
3631
this.config = {};
37-
this.monitor = !!options.monitor;
38-
this.loaderConfig = loaderConfig;
39-
this.loaded = false;
32+
33+
/**
34+
* Set up our child locations. They manage the act of getting all of the
35+
* files in a given "location" and merging them together properly.
36+
* Our job here is to merge the already-resolved configs together in priority
37+
* order
38+
*/
39+
this.locations = loaderConfig.locations.map((locationName) => new ConfigLocation(locationName, opts));
4040
}
4141

4242
/**
@@ -45,7 +45,7 @@ class ConfigLoader extends PatternEventEmitter {
4545
* @return {Config} this config object.
4646
*/
4747
stop() {
48-
this.__loaders.forEach((l) => l.unWatch());
48+
this.locations.forEach((l) => l.stop());
4949
return this;
5050
}
5151

@@ -63,10 +63,8 @@ class ConfigLoader extends PatternEventEmitter {
6363
if (this.__loaded) {
6464
return Promise.resolve(this.config);
6565
}
66-
return loader.createLoadersAsync(this.loaderConfig, { monitor: this.monitor }).then((loaders) => {
67-
this.__loaders = loaders;
68-
const loads = loaders.map((l) => l.load());
69-
return Promise.all(loads).then((configs) => this.__postLoad(configs));
66+
return Promise.all(this.locations.map((location) => location.load())).then((configs) => {
67+
return this.__postLoad(configs);
7068
});
7169
}
7270

@@ -83,18 +81,16 @@ class ConfigLoader extends PatternEventEmitter {
8381
if (this.__loaded) {
8482
return this.config;
8583
}
86-
this.__loaders = loader.createLoadersSync(this.loaderConfig, { monitor: this.monitor });
87-
return this.__postLoad(this.__loaders.map((l) => l.loadSync()));
84+
return this.__postLoad(this.locations.map((location) => location.loadSync()));
8885
}
8986

9087
__mergeConfigs(configs) {
91-
return processor(_.cloneDeep(this.config), configs, {
92-
environment: this.environment,
93-
defaultEnvironment: this.defaultEnvironment,
94-
nodeType: this.nodeType,
95-
eventEmitter: this,
96-
environmentVariables: this.environmentVariables,
97-
});
88+
const mergedConfigs = configs.reduce((merged, source) => _.merge(merged, source), {});
89+
/**
90+
* We can't just use the `mergedConfigs` here because the `merger` manages our event
91+
* emitter.
92+
*/
93+
return merger(this.config, mergedConfigs, { eventEmitter: this });
9894
}
9995

10096
/**
@@ -111,8 +107,12 @@ class ConfigLoader extends PatternEventEmitter {
111107
}
112108

113109
__listenToChanges() {
114-
this.__loaders.forEach((l) =>
110+
this.locations.forEach((l) =>
115111
l.on('change', (path, config) => {
112+
/**
113+
* When one of our locations reports a change to their
114+
* config, merge it in.
115+
*/
116116
this.config = this.__mergeConfigs([config]);
117117
}),
118118
);

‎lib/loader/index.js

+20-29
Original file line numberDiff line numberDiff line change
@@ -23,42 +23,33 @@ const createLoader = (options) => {
2323
throw new Error('Unable to determine loader type please specify a file location');
2424
};
2525

26-
const normalizeLocations = (locations, options) =>
27-
locations.map((location) => _.merge({}, options, _.isString(location) ? { file: location } : location));
28-
2926
const createLocationsFromGlobedFiles = (location, globedFiles) =>
3027
globedFiles.map((file) => _.merge({}, location, { file }));
3128

32-
const createLoadersSync = (loaderConfig, options) => {
33-
let loaders = [];
34-
if (loaderConfig.locations) {
35-
const files = normalizeLocations(loaderConfig.locations, options).reduce((globedFiles, location) => {
36-
const globPattern = createGlobPattern(location.file);
37-
const filesFromGlob = glob.sync(globPattern);
38-
return [...globedFiles, ...createLocationsFromGlobedFiles(location, filesFromGlob)];
39-
}, []);
40-
loaders = files.map((location) => createLoader(location));
41-
}
42-
return loaders;
29+
const createLoadersSync = (locationName, options) => {
30+
const globPattern = createGlobPattern(locationName.file);
31+
const filesFromGlob = glob.sync(globPattern);
32+
const files = createLocationsFromGlobedFiles(locationName, filesFromGlob);
33+
return files.map((file) =>
34+
createLoader({
35+
...options,
36+
...file,
37+
}),
38+
);
4339
};
4440

45-
const createLoadersAsync = (loaderConfig, options) => {
46-
let loadersPromise = Promise.resolve([]);
47-
if (loaderConfig.locations) {
48-
const globFilesPromise = normalizeLocations(loaderConfig.locations, options).reduce(
49-
(globedFilesPromise, location) =>
50-
globedFilesPromise.then((globedFiles) => {
51-
const globPattern = createGlobPattern(location.file);
52-
return globP(globPattern).then((filesFromGlob) => [
53-
...globedFiles,
54-
...createLocationsFromGlobedFiles(location, filesFromGlob),
55-
]);
41+
const createLoadersAsync = (locationName, options) => {
42+
const globPattern = createGlobPattern(locationName.file);
43+
return globP(globPattern)
44+
.then((filesFromGlob) => createLocationsFromGlobedFiles(locationName, filesFromGlob))
45+
.then((files) =>
46+
files.map((file) =>
47+
createLoader({
48+
...options,
49+
...file,
5650
}),
57-
Promise.resolve([]),
51+
),
5852
);
59-
loadersPromise = globFilesPromise.then((files) => files.map((location) => createLoader(location)));
60-
}
61-
return loadersPromise;
6253
};
6354

6455
module.exports = {

‎lib/processor/processor.js

+4-8
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,11 @@ const merger = require('./merger');
44
const selector = require('./selector');
55
const mixin = require('./mixin');
66

7-
const processConfig = (config, options) => {
8-
const merged = mixin(config, options);
9-
const mergeEnvConfig = selector(merged, options);
10-
return replacer(mergeEnvConfig, options);
11-
};
12-
137
const processor = (configToMergeInto, sourceConfigs, options) => {
14-
const mergedSource = sourceConfigs.reduce((merged, source) => _.merge(merged, processConfig(source, options)), {});
15-
return merger(configToMergeInto, mergedSource, options);
8+
const mergedSource = sourceConfigs.reduce((merged, source) => _.merge(merged, mixin(source, options)), {});
9+
const mergeEnvConfig = selector(mergedSource, options);
10+
const replaced = replacer(mergeEnvConfig, options);
11+
return merger(configToMergeInto, replaced, options);
1612
};
1713

1814
module.exports = processor;

0 commit comments

Comments
 (0)
This repository has been archived.