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

@yinheli/envmerge

v0.1.1

Published

A CLI tool to merge multiple .env files while preserving comments and handling conflicts intelligently

Downloads

20

Readme

envmerge

A CLI tool to intelligently merge multiple .env files while preserving comments, handling conflicts, and maintaining file structure.

CI npm version JSR License: MIT

Features

  • 🔄 Smart Merging: Merge multiple .env files with configurable priority
  • 🎯 Intelligent Positioning: New variables are automatically grouped with related variables based on comments and naming patterns
  • 💬 Preserve Comments: Keep all comments and empty lines from source files
  • 🔒 Automatic Backups: Creates timestamped backups before modifying destination files
  • Conflict Resolution: Choose between interactive, overwrite, or keep strategies

Installation

npm

npm install -g @yinheli/envmerge

JSR (Deno)

deno install -Agf jsr:@yinheli/envmerge

Bun

bun install -g @yinheli/envmerge

Note: After installation, the CLI command is available as envmerge.

Usage

Basic Usage

Merge multiple source files into a destination file:

envmerge .env.base .env.local .env

This will:

  1. Read .env.base and .env.local (in that order)
  2. Merge them with later files taking priority
  3. Create a backup of .env if it exists
  4. Interactively resolve any conflicts with existing values in .env
  5. Write the merged result to .env

Command Line Options

envmerge [options] <sources...> <destination>

Arguments:
  sources       Source .env files to merge (in priority order, left to right)
  destination   Destination .env file path

Options:
  -V, --version              Output the version number
  -h, --help                 Display help information
  --no-backup                Skip creating a backup of the destination file
  -s, --strategy <type>      Conflict resolution strategy (choices: "interactive", "overwrite", "keep", default: "interactive")

Examples

Merge with automatic overwrite

envmerge --strategy overwrite .env.defaults .env.production .env

Merge without backup

envmerge --no-backup .env.base .env.local .env

Keep existing values on conflicts

envmerge --strategy keep .env.template .env

Interactive conflict resolution (default)

envmerge .env.base .env.local .env

When conflicts are detected, If you choose individual review, you'll see:

Conflict for key: DATABASE_URL
  Current (destination): postgresql://localhost/old_db
  New (source):          postgresql://localhost/new_db

? Which value should be used?
❯ Use new value: postgresql://localhost/new_db
  Keep current value: postgresql://localhost/old_db

Conflict Resolution Strategies

interactive (default)

Prompts you to resolve each conflict individually or apply a bulk resolution (overwrite all or keep all). Best for manual review and control.

overwrite

Automatically uses values from source files, overwriting any conflicting values in the destination. Best for automated deployments where source files are authoritative.

keep

Preserves all existing values in the destination file, only adding new variables from sources. Best when you want to add new variables without modifying existing configuration.

How It Works

Merge Priority

When merging multiple source files, later files override earlier files:

envmerge .env.base .env.dev .env.local .env

Priority order (highest to lowest):

  1. .env.local (highest priority)
  2. .env.dev
  3. .env.base
  4. .env (destination - only for variables not in sources)

Conflict Detection

A conflict occurs when:

  • A variable exists in both the merged sources and the destination file
  • The values are different

Backup Naming

Backups are created with the format:

.env-backup-envmerge-YYYYMMDDHHmmss

Example: .env-backup-envmerge-20250106123045

Comment and Structure Preservation

envmerge preserves:

  • All comments (lines starting with #)
  • Empty lines
  • Original formatting and order from source files
  • Destination file structure when merging

Example:

Input (.env.base):

# Database Configuration
DB_HOST=localhost
DB_PORT=5432

# API Keys
API_KEY=base_key

Input (.env.local):

# Override for local development
API_KEY=local_key
API_SECRET=secret123

Output (.env):

# Database Configuration
DB_HOST=localhost
DB_PORT=5432

# API Keys
API_KEY=local_key
API_SECRET=secret123

Intelligent Variable Positioning

envmerge uses smart positioning algorithms to place new variables in logical locations:

Comment Group Matching

New variables are inserted near variables with matching comment groups:

Destination (.env):

# Database
DB_HOST=localhost

# API
API_URL=http://api.example.com

Source (.env.local):

# Database
DB_PORT=5432
DB_USER=admin

Result (.env):

# Database
DB_HOST=localhost
DB_PORT=5432     # ← Automatically grouped with DB_HOST
DB_USER=admin    # ← Automatically grouped with DB_HOST

# API
API_URL=http://api.example.com

Variable Prefix Matching

Variables with similar prefixes (like DB_*, API_*) are grouped together even without matching comments:

Destination (.env):

DB_HOST=localhost
API_URL=http://api

Source (.env.local):

DB_PORT=5432
API_KEY=secret

Result (.env):

DB_HOST=localhost
DB_PORT=5432     # ← Grouped by DB_ prefix
API_URL=http://api
API_KEY=secret   # ← Grouped by API_ prefix

Source File Order Preservation

The relative order of variables from source files is always preserved:

# Source has: FIRST=1, SECOND=2, THIRD=3
# Result maintains: FIRST, SECOND, THIRD in the same order

Development

Prerequisites

  • Bun (recommended) or Node.js >=18
  • TypeScript knowledge

Setup

# Clone the repository
git clone https://github.com/yinheli/envmerge.git
cd envmerge

# Install dependencies
bun install

# Run tests
bun test

# Run tests with coverage
bun run test:coverage

# Type check
bun run typecheck

# Build
bun run build

# Run locally
bun run dev .env.example .env

Testing

The project uses Vitest for testing with a comprehensive test suite:

# Run all tests
bun test

# Watch mode
bun run test:watch

# Coverage report
bun run test:coverage

API

While primarily a CLI tool, you can also use envmerge programmatically:

import { mergeSources, parseEnvFile, writeEnvFile } from "@yinheli/envmerge";
import { readFileSync } from "node:fs";

// Parse files (returns linked list of blocks)
const source1 = parseEnvFile(readFileSync(".env.base", "utf-8"));
const source2 = parseEnvFile(readFileSync(".env.local", "utf-8"));

// Merge (source2 overrides source1 for existing variables)
const merged = mergeSources(source1, source2);

// Write result
writeEnvFile(".env", merged);

Advanced Usage

The library uses a linked list data structure to represent .env files as blocks. Each block can be a variable, comment, or empty line:

import type { Block } from "@yinheli/envmerge";
import { parseEnvFile, serializeToString } from "@yinheli/envmerge";

const content = readFileSync(".env", "utf-8");
const head = parseEnvFile(content);

// Traverse the linked list
let current = head;
while (current) {
  switch (current.type) {
    case "variable":
      console.log(`${current.key}=${current.value}`);
      console.log(`Comments: ${current.comments.join("\n")}`);
      break;
    case "comment":
      console.log(`Comment block: ${current.lines.join("\n")}`);
      break;
    case "empty":
      console.log("Empty line");
      break;
  }
  current = current.next;
}

// Convert back to string
const output = serializeToString(head);

Type Definitions

type CommentBlock = {
  type: "comment";
  lines: string[];
  next: Block | null;
};

type EmptyBlock = {
  type: "empty";
  next: Block | null;
};

type VariableBlock = {
  type: "variable";
  key: string;
  value: string;
  raw: string;
  comments: string[]; // Comments immediately before this variable
  next: Block | null;
};

type Block = CommentBlock | EmptyBlock | VariableBlock;

Contributors

Contributors

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT © yinheli

Related Projects

  • dotenv - Load environment variables from .env files
  • dotenv-expand - Variable expansion for dotenv
  • env-cmd - Execute commands with specific environment variables