@yinheli/envmerge
v0.1.1
Published
A CLI tool to merge multiple .env files while preserving comments and handling conflicts intelligently
Downloads
20
Maintainers
Readme
envmerge
A CLI tool to intelligently merge multiple .env files while preserving
comments, handling conflicts, and maintaining file structure.
Features
- 🔄 Smart Merging: Merge multiple
.envfiles 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/envmergeJSR (Deno)
deno install -Agf jsr:@yinheli/envmergeBun
bun install -g @yinheli/envmergeNote: 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 .envThis will:
- Read
.env.baseand.env.local(in that order) - Merge them with later files taking priority
- Create a backup of
.envif it exists - Interactively resolve any conflicts with existing values in
.env - 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 .envMerge without backup
envmerge --no-backup .env.base .env.local .envKeep existing values on conflicts
envmerge --strategy keep .env.template .envInteractive conflict resolution (default)
envmerge .env.base .env.local .envWhen 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_dbConflict 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 .envPriority order (highest to lowest):
.env.local(highest priority).env.dev.env.base.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-YYYYMMDDHHmmssExample: .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_keyInput (.env.local):
# Override for local development
API_KEY=local_key
API_SECRET=secret123Output (.env):
# Database Configuration
DB_HOST=localhost
DB_PORT=5432
# API Keys
API_KEY=local_key
API_SECRET=secret123Intelligent 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.comSource (.env.local):
# Database
DB_PORT=5432
DB_USER=adminResult (.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.comVariable Prefix Matching
Variables with similar prefixes (like DB_*, API_*) are grouped together even
without matching comments:
Destination (.env):
DB_HOST=localhost
API_URL=http://apiSource (.env.local):
DB_PORT=5432
API_KEY=secretResult (.env):
DB_HOST=localhost
DB_PORT=5432 # ← Grouped by DB_ prefix
API_URL=http://api
API_KEY=secret # ← Grouped by API_ prefixSource 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 orderDevelopment
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 .envTesting
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:coverageAPI
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
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT © yinheli
Related Projects
- dotenv - Load environment variables from
.envfiles - dotenv-expand - Variable expansion for dotenv
- env-cmd - Execute commands with specific environment variables
