UNPKG

5.43 kBJavaScriptView Raw
1'use strict';
2
3var path = require('path');
4var nconf = require('nconf');
5var shush = require('shush');
6var caller = require('caller');
7var thing = require('core-util-is');
8var shortstop = require('shortstop');
9var debug = require('debuglog')('confit');
10var env = require('./lib/env');
11var util = require('./lib/util');
12
13
14/**
15 * Initializes environment convenience props in the provided nconf provider.
16 * @param config an nconf Provider.
17 * @returns {Object} the newly configured nconf Provider.
18 */
19function environment(nodeEnv) {
20 var data = {};
21
22 debug('NODE_ENV set to \'%s\'', nodeEnv);
23
24 // Normalize env and set convenience values.
25 Object.keys(env).forEach(function (current) {
26 var match;
27
28 match = env[current].test(nodeEnv);
29 if (match) { nodeEnv = current; }
30
31 data[current] = match;
32 });
33
34 debug('env:env set to \'%s\'', nodeEnv);
35
36 // Set (or re-set) env:{nodeEnv} value in case
37 // NODE_ENV was not one of our predetermined env
38 // keys (so `config.get('env:blah')` will be true).
39 data[nodeEnv] = true;
40 data.env = nodeEnv;
41 return { env: data };
42}
43
44
45/**
46 * Creates a local nconf provider instance. NO GLOBAL!
47 * @returns {Object} an nconf provider
48 */
49function provider() {
50 var config;
51
52 config = new nconf.Provider();
53 config.add('argv');
54 config.add('env');
55
56 // Put override before memory to ensure env
57 // values are immutable.
58 config.overrides({
59 type: 'literal',
60 store: environment(config.get('NODE_ENV') || 'development')
61 });
62
63 config.add('memory');
64
65 return config;
66}
67
68
69/**
70 * Creates a file loader that uses the provided `basedir`.
71 * @param basedir the root directory against which file paths will be resolved.
72 * @returns {Function} the file loader implementation.
73 */
74function loader(basedir) {
75
76 return function load(file) {
77 var name, config;
78
79 name = path.basename(file, path.extname(file));
80 config = path.join(basedir, file);
81
82 return {
83 name: name,
84 data: shush(config)
85 };
86 };
87
88}
89
90
91/**
92 * Wraps the provided nconf Provider in a simpler convenience API.
93 * @param config an nconf Provider.
94 */
95function wrap(config) {
96 return {
97
98 get: function get(key) {
99 return config.get(key);
100 },
101
102 set: function set(key, value) {
103 // NOTE: There was discussion around potentially warning
104 // on attempts to set immutable values. The would require
105 // a minimum of one additional operation, which was deemed
106 // overkill for a small/unlikely scenrio. Can revisit.
107 config.set(key, value);
108 },
109
110 use: function use(obj) {
111 // Merge into memory store.
112 // This must be done b/c nconf applies things kind of backward.
113 // If we just used a literal store it would get added to the END
114 // so no values would be overridden. Additionally, only the memory
115 // store is writable at this point so all updates live there.
116 config.merge(obj);
117 }
118
119 };
120}
121
122
123/**
124 * Main module entrypoint. Creates a confit config object using the provided
125 * options.
126 * @param options the configuration settings for this config instance.
127 * @param callback the function to which error or config object will be passed.
128 */
129module.exports = function confit(options, callback) {
130 var shorty, config, tasks, load;
131
132 // Normalize arguments
133 if (thing.isFunction(options)) {
134 callback = options;
135 options = undefined;
136 }
137
138 // ... still normalizing
139 if (thing.isString(options)) {
140 options = { basedir: options };
141 }
142
143 // ¯\_(ツ)_/¯ ... still normalizing
144 options = options || {};
145 options.defaults = options.defaults || 'config.json';
146 options.basedir = options.basedir || path.dirname(caller());
147
148
149 // Configure shortstop using provided protocols
150 shorty = shortstop.create();
151 if (thing.isObject(options.protocols)) {
152 Object.keys(options.protocols).forEach(function (protocol) {
153 shorty.use(protocol, options.protocols[protocol]);
154 });
155 }
156
157 // Create config provider and initialize basedir
158 // TODO: Add basedir to overrides so it's readonly?
159 config = provider();
160 config.set('basedir', options.basedir);
161
162
163 tasks = [];
164 load = loader(options.basedir);
165
166
167 // Load the env-specific config file as a literal
168 // datastore. Can't use `file` b/c we preprocess it.
169 tasks.push(function (done) {
170 var file = load(config.get('env:env') + '.json');
171 config.use(file.name, {
172 type: 'literal',
173 store: shorty.resolve(file.data)
174 });
175 done();
176 });
177
178
179 // Set defaults from `defaults` file.
180 tasks.push(function (done) {
181 var file = load(options.defaults);
182 config.defaults(shorty.resolve(file.data));
183 done();
184 });
185
186
187 util.each(tasks, function (err) {
188 // XXX: Force async until shortstop@1.0 is integrated.
189
190 // Only report unusual errors. MODULE_NOT_FOUND is an
191 // acceptable scenario b/c no files are truly requried.
192 if (thing.isObject(err) && err.code !== 'MODULE_NOT_FOUND') {
193 setImmediate(callback.bind(null, err));
194 return;
195 }
196
197 config = wrap(config);
198 setImmediate(callback.bind(null, null, config));
199 });
200
201};