@energma/config-merge-sdk
v1.0.0
Published
Framework-agnostic hierarchical config merge SDK with multi-tenant support, $ref resolution, pluggable adapters, and CLI scaffolding.
Maintainers
Readme
Config Merge SDK
Framework-agnostic hierarchical config merge SDK with multi-tenant support, $ref resolution, pluggable adapters, and CLI scaffolding.
Features
- 🏗️ Hierarchical Merging - Base + Target + Environment configs
- 🔗 $ref Resolution - Reference other config files or external sources
- 🎯 Multi-tenant Support - Manage multiple targets from a single config base
- 🔌 Pluggable Adapters - AWS SSM, environment variables, custom sources
- 🎨 Plugin System - Transform configs with custom logic
- 📋 Schema Validation - Define and validate config structure
- ⚡ CLI Tools - Scaffold, build, and validate configs
- 🔄 Deep Merge - Smart merging with array strategies
Installation
npm install @energma/config-merge-sdkQuick Start
1. Initialize Project
npx config-merge-sdk initThis creates:
configs/
_base/
defaults/ # Base configuration (shared)
schemas/ # Type schemas (optional)
targets/ # Target-specific configs
env/ # Environment overrides
dev.yaml
prod.yaml
config-merge-sdk.config.js2. Create a Target
npx config-merge-sdk init target my-appCreates: configs/targets/my-app/
3. Add Configuration
Edit files in configs/targets/my-app/:
// configs/targets/my-app/app.json
{
"appName": "My Application",
"version": "1.0.0",
"features": {
"auth": true,
"notifications": true
}
}4. Build Config
npx config-merge-sdk build --env dev --target my-appUsage
Programmatic API
import { buildConfig } from '@energma/config-merge-sdk';
const result = await buildConfig({
configDir: './configs',
baseDir: '_base',
targetsDir: 'targets',
envDir: 'env',
environment: 'dev',
target: 'my-app',
output: './output/config.json'
});
console.log(result['my-app']); // Built configurationDeep Merge
import { deepMerge } from '@energma/config-merge-sdk';
const base = {
features: { auth: true },
servers: ['server1']
};
const override = {
features: { notifications: true },
servers: ['server2']
};
const merged = deepMerge(base, override, {
arrayMergeStrategy: 'concat' // or 'replace'
});$ref Resolution
Reference other files:
// configs/targets/my-app/app.json
{
"database": { "$ref": "./database.json" },
"features": { "$ref": "../shared/features.json" }
}Resolve references:
import { resolveRefs } from '@energma/config-merge-sdk';
const resolved = resolveRefs(config, {
basePath: './configs/targets/my-app'
});Adapters
Environment Variables Adapter
import { buildConfig, envAdapter } from '@energma/config-merge-sdk';
const result = await buildConfig({
configDir: './configs',
environment: 'dev',
adapters: [
envAdapter({
prefix: 'APP_',
mapping: {
'database.host': 'APP_DB_HOST',
'database.port': 'APP_DB_PORT'
},
priority: 100
})
]
});AWS SSM Adapter
import { ssmAdapter } from '@energma/config-merge-sdk/adapters';
const result = await buildConfig({
configDir: './configs',
environment: 'prod',
adapters: [
ssmAdapter({
region: 'us-east-1',
paths: ['/myapp/prod/'],
priority: 100
})
]
});Custom Adapter
const customAdapter = {
name: 'my-adapter',
priority: 50,
async resolve(config, context) {
// Fetch from API, database, etc.
const externalConfig = await fetchFromApi();
return { ...config, ...externalConfig };
}
};
const result = await buildConfig({
configDir: './configs',
adapters: [customAdapter]
});Plugins
Filter Plugin
import { buildConfig, filterPlugin } from '@energma/config-merge-sdk/plugins';
const result = await buildConfig({
configDir: './configs',
plugins: [
filterPlugin({
include: ['features', 'settings'],
exclude: ['internal']
})
]
});Flatten Plugin
import { flattenPlugin } from '@energma/config-merge-sdk/plugins';
const result = await buildConfig({
configDir: './configs',
plugins: [
flattenPlugin({
delimiter: '_',
maxDepth: 3
})
]
});Custom Plugin
const customPlugin = {
name: 'my-plugin',
transform(config, context) {
// Transform config after merging
return {
...config,
timestamp: new Date().toISOString()
};
}
};CLI Commands
Initialize Project
config-merge-sdk initCreates the base project structure with example configs.
Create Target
config-merge-sdk init target <name>Scaffolds a new target directory.
Build Config
# Build specific target for environment
config-merge-sdk build --env dev --target my-app --output ./dist/config.json
# Build all targets
config-merge-sdk build --env prod
# Build with custom config directory
config-merge-sdk build --config ./my-configs --env devList Targets
config-merge-sdk listShows all available targets in configs/targets/.
Validate Config
config-merge-sdk synthValidates configuration structure against schemas.
Configuration File
config-merge-sdk.config.js:
export default {
configDir: './configs',
baseDir: '_base',
targetsDir: 'targets',
envDir: 'env',
defaultsDir: 'defaults',
environment: 'dev',
output: './output/config.json',
};Directory Structure
Recommended Structure
configs/
_base/
defaults/ # Base configuration (shared)
app.json
database.json
schemas/ # Validation schemas (optional)
app.yaml
targets/ # Target-specific configs
my-app/
app.json # Override base config
features.json
my-app-staging/
app.json
env/ # Environment-specific overrides
dev.yaml
staging.yaml
prod.yamlMerge Order
Configs are merged in this order (later overrides earlier):
- Base Defaults (
_base/defaults/) - Target Config (
targets/<target>/) - Environment Config (
env/<environment>.yaml) - Adapters (e.g., environment variables, AWS SSM)
- Plugins (transformations)
Schema Validation
Define schemas in _base/schemas/:
# _base/schemas/app.yaml
app:
name: string! # Required string
version: string! # Required string
port: number # Optional number
enabled: boolean # Optional boolean
features: object # Optional object
tags: array # Optional arraySupported types:
string,string!(required)number,number!boolean,boolean!array,array!object,object!
Advanced Usage
Multi-tenant Application
configs/
_base/
defaults/
app.json # Shared config
targets/
tenant-acme/
branding.json # ACME branding
features.json # ACME-specific features
tenant-globex/
branding.json # Globex branding
features.json # Globex-specific featuresBuild for each tenant:
config-merge-sdk build --target tenant-acme --env prod
config-merge-sdk build --target tenant-globex --env prodEnvironment-specific Overrides
# env/dev.yaml
database:
host: localhost
port: 5432
debug: true
# env/prod.yaml
database:
host: prod-db.example.com
port: 5432
debug: false$ref with Wildcards
{
"blogs": { "$ref": "./blog/articles/*.json" }
}This loads all JSON files in blog/articles/ and merges them into an array.
API Reference
buildConfig(options)
Build configuration from hierarchical sources.
Options:
configDir(string) - Base config directorybaseDir(string) - Base config subdirectory (default:_base)targetsDir(string) - Targets subdirectory (default:targets)envDir(string) - Environment subdirectory (default:env)defaultsDir(string) - Defaults subdirectory (default:defaults)environment(string) - Environment name (e.g.,dev,prod)target(string) - Target name (optional, builds all if omitted)output(string) - Output file path (optional)adapters(array) - Adapter instancesplugins(array) - Plugin instancesmergeOptions(object) - Deep merge optionsrefOptions(object) - $ref resolution options
Returns: Promise<ConfigOutput> - Object keyed by target name
deepMerge(target, source, options)
Deep merge two objects.
Options:
arrayMergeStrategy-'concat'|'replace'(default:'replace')
resolveRefs(config, options)
Resolve $ref references in config.
Options:
basePath(string) - Base path for relative referencesmaxDepth(number) - Maximum recursion depth (default: 10)
Examples
Real-world Example: Energma Marketing Site
// Build script using config-merge-sdk
const { buildConfig } = require('@energma/config-merge-sdk');
async function build() {
const result = await buildConfig({
configDir: './configs',
baseDir: '_base',
targetsDir: 'targets',
environment: process.env.NODE_ENV || 'dev',
target: 'default',
mergeOptions: {
arrayMergeStrategy: 'replace'
}
});
// Flatten and restructure for app
const config = result.default;
// Write to output
fs.writeFileSync(
'./configv2.json',
JSON.stringify(config, null, 2)
);
}Troubleshooting
Config not found
Error: Target "my-app" not foundSolution: Verify target directory exists:
ls configs/targets/my-app$ref not resolving
Error: Cannot resolve reference: ./missing.jsonSolution: Check that referenced file exists and path is correct.
Adapter priority conflicts
Adapters run in priority order (lower number = higher priority).
adapters: [
{ priority: 10, ... }, // Runs first
{ priority: 50, ... }, // Runs second
{ priority: 100, ... } // Runs last
]Contributing
See CONTRIBUTING.md
License
MIT
