npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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.

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-sdk

Quick Start

1. Initialize Project

npx config-merge-sdk init

This 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.js

2. Create a Target

npx config-merge-sdk init target my-app

Creates: 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-app

Usage

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 configuration

Deep 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 init

Creates 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 dev

List Targets

config-merge-sdk list

Shows all available targets in configs/targets/.

Validate Config

config-merge-sdk synth

Validates 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.yaml

Merge Order

Configs are merged in this order (later overrides earlier):

  1. Base Defaults (_base/defaults/)
  2. Target Config (targets/<target>/)
  3. Environment Config (env/<environment>.yaml)
  4. Adapters (e.g., environment variables, AWS SSM)
  5. 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 array

Supported 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 features

Build for each tenant:

config-merge-sdk build --target tenant-acme --env prod
config-merge-sdk build --target tenant-globex --env prod

Environment-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 directory
  • baseDir (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 instances
  • plugins (array) - Plugin instances
  • mergeOptions (object) - Deep merge options
  • refOptions (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 references
  • maxDepth (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 found

Solution: Verify target directory exists:

ls configs/targets/my-app

$ref not resolving

Error: Cannot resolve reference: ./missing.json

Solution: 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

Related