@bunli/plugin-config
Configuration loading and merging plugin for Bunli
@bunli/plugin-config
The config plugin provides automatic configuration loading and merging from multiple sources, making it easy to support user preferences and project-specific settings.
Installation
bun add @bunli/plugin-config
Features
- Load configuration from multiple file sources
- Support for JSON and RC file formats
- Deep or shallow merging strategies
- Template variable substitution
- User home directory support
- Flexible source patterns
Basic Usage
import { createCLI } from '@bunli/core'
import { configMergerPlugin } from '@bunli/plugin-config'
const cli = await createCLI({
name: 'my-cli',
plugins: [
configMergerPlugin()
]
})
Configuration Sources
By default, the plugin looks for configuration in these locations (in order):
~/.config/{{name}}/config.json
- User config directory.{{name}}rc
- Project RC file.{{name}}rc.json
- Project RC file (JSON).config/{{name}}.json
- Project config directory
The {{name}}
template is replaced with your CLI name.
Options
interface ConfigPluginOptions {
/**
* Config file sources to load
* Supports template variables: {{name}} for app name
* Default: ['~/.config/{{name}}/config.json', '.{{name}}rc', '.{{name}}rc.json']
*/
sources?: string[]
/**
* Merge strategy
* - 'deep': Recursively merge objects (default)
* - 'shallow': Only merge top-level properties
*/
mergeStrategy?: 'shallow' | 'deep'
/**
* Whether to stop on first found config
* Default: false (loads and merges all found configs)
*/
stopOnFirst?: boolean
}
Examples
Custom Sources
configMergerPlugin({
sources: [
'~/.myapp/config.json',
'./myapp.config.json',
'./config/myapp.json'
]
})
Shallow Merge
configMergerPlugin({
mergeStrategy: 'shallow'
})
Stop on First Config
configMergerPlugin({
sources: [
'./local.config.json',
'~/.config/{{name}}/config.json'
],
stopOnFirst: true // Use local config if it exists
})
Configuration File Format
Configuration files should be valid JSON:
{
"name": "my-app",
"version": "2.0.0",
"description": "Overridden by config",
"customField": {
"nested": {
"value": 123
}
}
}
Merge Behavior
Deep Merge (Default)
With deep merge, nested objects are recursively merged:
// File 1: ~/.config/myapp/config.json
{
"api": {
"url": "https://api.example.com",
"timeout": 5000
}
}
// File 2: .myapprc.json
{
"api": {
"timeout": 10000,
"retries": 3
}
}
// Result:
{
"api": {
"url": "https://api.example.com",
"timeout": 10000,
"retries": 3
}
}
Shallow Merge
With shallow merge, only top-level properties are merged:
// File 1: ~/.config/myapp/config.json
{
"api": {
"url": "https://api.example.com",
"timeout": 5000
}
}
// File 2: .myapprc.json
{
"api": {
"timeout": 10000,
"retries": 3
}
}
// Result:
{
"api": {
"timeout": 10000,
"retries": 3
}
// Note: url is lost with shallow merge
}
Best Practices
1. Order Sources by Priority
Place more specific configs later in the sources array:
configMergerPlugin({
sources: [
'~/.config/{{name}}/config.json', // User defaults
'.{{name}}rc.json', // Project config
'.{{name}}rc.local.json' // Local overrides
]
})
2. Document Config Schema
Create a config schema for your users:
interface MyAppConfig {
api: {
url: string
timeout?: number
retries?: number
}
features: {
experimental?: boolean
telemetry?: boolean
}
}
3. Provide Config Examples
Include example configuration files in your project:
// .myapprc.example.json
{
"api": {
"url": "https://api.example.com",
"timeout": 10000
},
"features": {
"experimental": false,
"telemetry": true
}
}
4. Handle Missing Configs
The plugin handles missing config files gracefully:
// No error thrown if config files don't exist
const cli = await createCLI({
name: 'my-cli',
plugins: [
configMergerPlugin({
sources: [
'./optional-config.json',
'~/.config/{{name}}/config.json'
]
})
]
})
Integration with Commands
Merged configuration is available in your CLI config:
const cli = await createCLI({
name: 'my-cli',
version: '1.0.0',
plugins: [configMergerPlugin()]
})
// Config values can override CLI properties
cli.command(defineCommand({
name: 'info',
handler: async ({ cli }) => {
console.log(`Name: ${cli.name}`)
console.log(`Version: ${cli.version}`)
// These might be overridden by config files
}
}))
Debugging
The plugin uses the logger to provide debugging information:
// Enable debug logging to see which configs are loaded
process.env.DEBUG = 'bunli:*'
configMergerPlugin({
sources: ['./config.json', '~/.config/app.json']
})
// Output:
// [bunli:plugin] Config file not found: ./config.json
// [bunli:plugin] Loaded config from ~/.config/app.json
// [bunli:plugin] Merged 1 config file(s)