Launch your command line tool with ease by handling configuration discovery, local module resolution, and process management.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Configuration file discovery and loading system supporting multiple formats, inheritance through extends, flexible search patterns, and custom file extension loaders.
Discovers configuration files based on search patterns, file extensions, and directory traversal rules defined in the Liftoff instance configuration.
/**
* Configuration file search specification
* Used in configFiles array to define how to search for config files
*/
interface ConfigFileSpec {
/** Base name of the config file to search for */
name?: string;
/** Path to search in (required) - can be relative or absolute */
path: string;
/** File extensions to try when searching */
extensions?: string[] | { [ext: string]: string | null };
/** Base directory for resolving relative paths */
cwd?: string;
/** Whether to search up the directory tree until file found */
findUp?: boolean;
}Usage Examples:
const Liftoff = require('liftoff');
// Configuration with multiple config file sources
const MyApp = new Liftoff({
name: 'myapp',
configFiles: [
// Look for .myapprc in home directory
{
name: '.myapprc',
path: '~',
extensions: { '.json': null, '.js': null }
},
// Look for myappfile.js in current directory and up the tree
{
name: 'myappfile',
path: '.',
findUp: true
},
// Look for project-specific config in custom location
{
name: 'project-config',
path: './config',
cwd: process.cwd(),
extensions: ['.json', '.yaml', '.js']
}
],
extensions: {
'.js': null,
'.json': null,
'.yaml': 'yaml-loader'
}
});
const env = MyApp.buildEnvironment();
console.log('Found config files:', env.configFiles);
// Might output:
// [
// '/home/user/.myapprc.json',
// '/project/myappfile.js',
// '/project/config/project-config.yaml'
// ]Loads configuration files and processes inheritance through the extends property, with support for deep merging and circular reference detection.
/**
* Configuration file structure with inheritance support
*/
interface ConfigurationFile {
/** Inherit from another configuration file */
extends?: string | ConfigFileSpec;
/** Override the discovered config path */
[configName: string]?: string;
/** Additional modules to preload */
preload?: string | string[];
/** Any other configuration properties */
[key: string]: any;
}Configuration Inheritance Process:
extends property exists, load the extended configurationextends property from final configurationUsage Examples:
// Base configuration file: base-config.js
module.exports = {
mode: 'development',
plugins: ['plugin-a', 'plugin-b'],
settings: {
verbose: true,
timeout: 5000
}
};
// Main configuration file: myappfile.js
module.exports = {
extends: './base-config.js',
mode: 'production', // Overrides base
plugins: ['plugin-c'], // Completely replaces base plugins array
settings: {
timeout: 10000 // Overrides timeout, keeps verbose: true
},
additionalSetting: 'value'
};
// Result after loading and merging:
// {
// mode: 'production',
// plugins: ['plugin-c'],
// settings: {
// verbose: true, // From base
// timeout: 10000 // From main config
// },
// additionalSetting: 'value'
// }Configuration files can override the discovered configuration path using a property that matches the configName.
/**
* Configuration path override mechanism
* A config file can specify an alternate config file to use
*/
interface ConfigPathOverride {
/** Property name matches the Liftoff configName */
[configName: string]: string;
}Usage Examples:
const MyApp = new Liftoff({
name: 'myapp', // configName becomes 'myappfile'
configFiles: [{ name: '.myapprc', path: '~' }]
});
// .myapprc.json content:
// {
// "myappfile": "./custom/path/to/config.js",
// "otherSettings": "value"
// }
const env = MyApp.buildEnvironment();
// env.configPath will be '/full/path/to/custom/path/to/config.js'
// The .myapprc config is used to find the real config, then the real config is loaded
// Relative paths are resolved from the directory containing the override config
// {
// "myappfile": "../configs/main.js" // Resolved relative to .myapprc location
// }Configuration files can specify additional modules to preload before CLI execution through the preload property.
/**
* Preload specification in configuration files
*/
interface PreloadConfiguration {
/** Single module to preload */
preload?: string;
/** Multiple modules to preload */
preload?: string[];
}Usage Examples:
// Configuration file with preload modules
module.exports = {
// String format for single module
preload: 'coffee-script/register',
// Other config properties
sourceMaps: true,
plugins: ['babel-plugin-transform-runtime']
};
// Configuration file with multiple preload modules
module.exports = {
// Array format for multiple modules
preload: [
'babel-register',
'source-map-support/register',
'coffee-script/register'
],
// Babel configuration for the preloaded babel-register
babel: {
presets: ['env']
}
};
// The preload modules are combined with any specified in buildEnvironment options
const env = MyApp.buildEnvironment({
preload: ['ts-node/register'] // Added to config-based preloads
});
// env.preload = ['ts-node/register', 'coffee-script/register']Configuration files support custom file extensions through registered loaders, allowing for TypeScript, CoffeeScript, YAML, and other formats.
/**
* File extension to loader mapping
* Loaders are Node.js modules that register file extension handlers
*/
interface ExtensionLoaders {
/** Extension with no loader (built-in Node.js support) */
[extension: string]: null;
/** Extension with loader module name */
[extension: string]: string;
}Usage Examples:
const MyApp = new Liftoff({
name: 'myapp',
extensions: {
'.js': null, // Native JavaScript
'.json': null, // Native JSON
'.coffee': 'coffee-script/register', // CoffeeScript
'.ts': 'ts-node/register', // TypeScript
'.yaml': 'js-yaml-loader', // YAML
'.toml': 'toml-require' // TOML
},
configFiles: [
{ name: 'myappfile', path: '.', findUp: true }
]
});
// Can now load any of these config files:
// - myappfile.js
// - myappfile.json
// - myappfile.coffee
// - myappfile.ts
// - myappfile.yaml
// - myappfile.toml
// The appropriate loader will be automatically required and registered
MyApp.on('loader:success', function(loaderName, module) {
console.log('Loaded extension loader:', loaderName);
});
MyApp.on('loader:failure', function(loaderName, error) {
console.error('Failed to load extension loader:', loaderName, error.message);
});Complex configuration scenarios including multiple inheritance levels, development vs production configs, and modular configuration structures.
Multi-level Inheritance:
// base.js - Foundation configuration
module.exports = {
core: {
timeout: 5000,
retries: 3
}
};
// development.js - Development overrides
module.exports = {
extends: './base.js',
core: {
debug: true,
timeout: 1000 // Faster timeout for development
},
devServer: {
port: 3000
}
};
// myappfile.js - Project-specific configuration
module.exports = {
extends: './development.js',
project: {
name: 'my-project',
version: '1.0.0'
},
core: {
retries: 5 // Override retries for this project
}
};
// Final merged configuration:
// {
// core: {
// timeout: 1000, // From development.js
// retries: 5, // From myappfile.js
// debug: true // From development.js
// },
// devServer: {
// port: 3000 // From development.js
// },
// project: {
// name: 'my-project',
// version: '1.0.0' // From myappfile.js
// }
// }Error Handling and Validation:
const MyApp = new Liftoff({
name: 'myapp',
configFiles: [{ name: 'myappfile', path: '.', findUp: true }]
});
try {
const env = MyApp.buildEnvironment();
// Validate configuration structure
if (env.config.length > 0) {
const config = env.config[0];
// Check for required properties
if (!config.mode) {
console.warn('No mode specified in configuration');
}
// Validate preload modules
if (config.preload && !Array.isArray(config.preload) && typeof config.preload !== 'string') {
console.error('Invalid preload configuration: must be string or array');
}
}
} catch (error) {
if (error.message.includes('circular extend')) {
console.error('Circular configuration inheritance detected');
console.error('Check your extends chain for loops');
} else if (error.message.includes('Unable to locate')) {
console.error('Configuration extends references missing file');
console.error('Verify the path in your extends property');
} else if (error.message.includes('Encountered error when loading')) {
console.error('Configuration file has syntax or runtime errors');
console.error('Check the configuration file for valid JavaScript/JSON');
} else {
console.error('Configuration loading failed:', error.message);
}
}