PackagesPlugin Packages
@bunli/plugin-config
Configuration loading and merging plugin for Bunli
Installation
bun add @bunli/plugin-configFeatures
- 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()] as const,
});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', '.config/{{name}}.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",
description: "Show CLI information",
handler: async ({ context, runtime }) => {
// Access config via plugin store
if (context?.store.config) {
console.log("Config loaded:", context.store.config);
}
},
}),
);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)