0
# Configuration Management
1
2
Configuration file discovery and loading system supporting multiple formats, inheritance through extends, flexible search patterns, and custom file extension loaders.
3
4
## Capabilities
5
6
### Configuration File Discovery
7
8
Discovers configuration files based on search patterns, file extensions, and directory traversal rules defined in the Liftoff instance configuration.
9
10
```javascript { .api }
11
/**
12
* Configuration file search specification
13
* Used in configFiles array to define how to search for config files
14
*/
15
interface ConfigFileSpec {
16
/** Base name of the config file to search for */
17
name?: string;
18
/** Path to search in (required) - can be relative or absolute */
19
path: string;
20
/** File extensions to try when searching */
21
extensions?: string[] | { [ext: string]: string | null };
22
/** Base directory for resolving relative paths */
23
cwd?: string;
24
/** Whether to search up the directory tree until file found */
25
findUp?: boolean;
26
}
27
```
28
29
**Usage Examples:**
30
31
```javascript
32
const Liftoff = require('liftoff');
33
34
// Configuration with multiple config file sources
35
const MyApp = new Liftoff({
36
name: 'myapp',
37
configFiles: [
38
// Look for .myapprc in home directory
39
{
40
name: '.myapprc',
41
path: '~',
42
extensions: { '.json': null, '.js': null }
43
},
44
45
// Look for myappfile.js in current directory and up the tree
46
{
47
name: 'myappfile',
48
path: '.',
49
findUp: true
50
},
51
52
// Look for project-specific config in custom location
53
{
54
name: 'project-config',
55
path: './config',
56
cwd: process.cwd(),
57
extensions: ['.json', '.yaml', '.js']
58
}
59
],
60
extensions: {
61
'.js': null,
62
'.json': null,
63
'.yaml': 'yaml-loader'
64
}
65
});
66
67
const env = MyApp.buildEnvironment();
68
console.log('Found config files:', env.configFiles);
69
// Might output:
70
// [
71
// '/home/user/.myapprc.json',
72
// '/project/myappfile.js',
73
// '/project/config/project-config.yaml'
74
// ]
75
```
76
77
### Configuration Loading and Inheritance
78
79
Loads configuration files and processes inheritance through the `extends` property, with support for deep merging and circular reference detection.
80
81
```javascript { .api }
82
/**
83
* Configuration file structure with inheritance support
84
*/
85
interface ConfigurationFile {
86
/** Inherit from another configuration file */
87
extends?: string | ConfigFileSpec;
88
/** Override the discovered config path */
89
[configName: string]?: string;
90
/** Additional modules to preload */
91
preload?: string | string[];
92
/** Any other configuration properties */
93
[key: string]: any;
94
}
95
```
96
97
**Configuration Inheritance Process:**
98
99
1. **File Loading**: Load the primary configuration file
100
2. **Extends Processing**: If `extends` property exists, load the extended configuration
101
3. **Recursive Loading**: Process extends in the extended file (with circular detection)
102
4. **Deep Merging**: Merge configurations with primary config taking precedence
103
5. **Cleanup**: Remove `extends` property from final configuration
104
105
**Usage Examples:**
106
107
```javascript
108
// Base configuration file: base-config.js
109
module.exports = {
110
mode: 'development',
111
plugins: ['plugin-a', 'plugin-b'],
112
settings: {
113
verbose: true,
114
timeout: 5000
115
}
116
};
117
118
// Main configuration file: myappfile.js
119
module.exports = {
120
extends: './base-config.js',
121
mode: 'production', // Overrides base
122
plugins: ['plugin-c'], // Completely replaces base plugins array
123
settings: {
124
timeout: 10000 // Overrides timeout, keeps verbose: true
125
},
126
additionalSetting: 'value'
127
};
128
129
// Result after loading and merging:
130
// {
131
// mode: 'production',
132
// plugins: ['plugin-c'],
133
// settings: {
134
// verbose: true, // From base
135
// timeout: 10000 // From main config
136
// },
137
// additionalSetting: 'value'
138
// }
139
```
140
141
### Configuration Path Override
142
143
Configuration files can override the discovered configuration path using a property that matches the `configName`.
144
145
```javascript { .api }
146
/**
147
* Configuration path override mechanism
148
* A config file can specify an alternate config file to use
149
*/
150
interface ConfigPathOverride {
151
/** Property name matches the Liftoff configName */
152
[configName: string]: string;
153
}
154
```
155
156
**Usage Examples:**
157
158
```javascript
159
const MyApp = new Liftoff({
160
name: 'myapp', // configName becomes 'myappfile'
161
configFiles: [{ name: '.myapprc', path: '~' }]
162
});
163
164
// .myapprc.json content:
165
// {
166
// "myappfile": "./custom/path/to/config.js",
167
// "otherSettings": "value"
168
// }
169
170
const env = MyApp.buildEnvironment();
171
// env.configPath will be '/full/path/to/custom/path/to/config.js'
172
// The .myapprc config is used to find the real config, then the real config is loaded
173
174
// Relative paths are resolved from the directory containing the override config
175
// {
176
// "myappfile": "../configs/main.js" // Resolved relative to .myapprc location
177
// }
178
```
179
180
### Preload Module Configuration
181
182
Configuration files can specify additional modules to preload before CLI execution through the `preload` property.
183
184
```javascript { .api }
185
/**
186
* Preload specification in configuration files
187
*/
188
interface PreloadConfiguration {
189
/** Single module to preload */
190
preload?: string;
191
/** Multiple modules to preload */
192
preload?: string[];
193
}
194
```
195
196
**Usage Examples:**
197
198
```javascript
199
// Configuration file with preload modules
200
module.exports = {
201
// String format for single module
202
preload: 'coffee-script/register',
203
204
// Other config properties
205
sourceMaps: true,
206
plugins: ['babel-plugin-transform-runtime']
207
};
208
209
// Configuration file with multiple preload modules
210
module.exports = {
211
// Array format for multiple modules
212
preload: [
213
'babel-register',
214
'source-map-support/register',
215
'coffee-script/register'
216
],
217
218
// Babel configuration for the preloaded babel-register
219
babel: {
220
presets: ['env']
221
}
222
};
223
224
// The preload modules are combined with any specified in buildEnvironment options
225
const env = MyApp.buildEnvironment({
226
preload: ['ts-node/register'] // Added to config-based preloads
227
});
228
// env.preload = ['ts-node/register', 'coffee-script/register']
229
```
230
231
### File Extension Support
232
233
Configuration files support custom file extensions through registered loaders, allowing for TypeScript, CoffeeScript, YAML, and other formats.
234
235
```javascript { .api }
236
/**
237
* File extension to loader mapping
238
* Loaders are Node.js modules that register file extension handlers
239
*/
240
interface ExtensionLoaders {
241
/** Extension with no loader (built-in Node.js support) */
242
[extension: string]: null;
243
/** Extension with loader module name */
244
[extension: string]: string;
245
}
246
```
247
248
**Usage Examples:**
249
250
```javascript
251
const MyApp = new Liftoff({
252
name: 'myapp',
253
extensions: {
254
'.js': null, // Native JavaScript
255
'.json': null, // Native JSON
256
'.coffee': 'coffee-script/register', // CoffeeScript
257
'.ts': 'ts-node/register', // TypeScript
258
'.yaml': 'js-yaml-loader', // YAML
259
'.toml': 'toml-require' // TOML
260
},
261
configFiles: [
262
{ name: 'myappfile', path: '.', findUp: true }
263
]
264
});
265
266
// Can now load any of these config files:
267
// - myappfile.js
268
// - myappfile.json
269
// - myappfile.coffee
270
// - myappfile.ts
271
// - myappfile.yaml
272
// - myappfile.toml
273
274
// The appropriate loader will be automatically required and registered
275
MyApp.on('loader:success', function(loaderName, module) {
276
console.log('Loaded extension loader:', loaderName);
277
});
278
279
MyApp.on('loader:failure', function(loaderName, error) {
280
console.error('Failed to load extension loader:', loaderName, error.message);
281
});
282
```
283
284
### Advanced Configuration Patterns
285
286
Complex configuration scenarios including multiple inheritance levels, development vs production configs, and modular configuration structures.
287
288
**Multi-level Inheritance:**
289
290
```javascript
291
// base.js - Foundation configuration
292
module.exports = {
293
core: {
294
timeout: 5000,
295
retries: 3
296
}
297
};
298
299
// development.js - Development overrides
300
module.exports = {
301
extends: './base.js',
302
core: {
303
debug: true,
304
timeout: 1000 // Faster timeout for development
305
},
306
devServer: {
307
port: 3000
308
}
309
};
310
311
// myappfile.js - Project-specific configuration
312
module.exports = {
313
extends: './development.js',
314
project: {
315
name: 'my-project',
316
version: '1.0.0'
317
},
318
core: {
319
retries: 5 // Override retries for this project
320
}
321
};
322
323
// Final merged configuration:
324
// {
325
// core: {
326
// timeout: 1000, // From development.js
327
// retries: 5, // From myappfile.js
328
// debug: true // From development.js
329
// },
330
// devServer: {
331
// port: 3000 // From development.js
332
// },
333
// project: {
334
// name: 'my-project',
335
// version: '1.0.0' // From myappfile.js
336
// }
337
// }
338
```
339
340
**Error Handling and Validation:**
341
342
```javascript
343
const MyApp = new Liftoff({
344
name: 'myapp',
345
configFiles: [{ name: 'myappfile', path: '.', findUp: true }]
346
});
347
348
try {
349
const env = MyApp.buildEnvironment();
350
351
// Validate configuration structure
352
if (env.config.length > 0) {
353
const config = env.config[0];
354
355
// Check for required properties
356
if (!config.mode) {
357
console.warn('No mode specified in configuration');
358
}
359
360
// Validate preload modules
361
if (config.preload && !Array.isArray(config.preload) && typeof config.preload !== 'string') {
362
console.error('Invalid preload configuration: must be string or array');
363
}
364
}
365
366
} catch (error) {
367
if (error.message.includes('circular extend')) {
368
console.error('Circular configuration inheritance detected');
369
console.error('Check your extends chain for loops');
370
} else if (error.message.includes('Unable to locate')) {
371
console.error('Configuration extends references missing file');
372
console.error('Verify the path in your extends property');
373
} else if (error.message.includes('Encountered error when loading')) {
374
console.error('Configuration file has syntax or runtime errors');
375
console.error('Check the configuration file for valid JavaScript/JSON');
376
} else {
377
console.error('Configuration loading failed:', error.message);
378
}
379
}
380
```