🐹

env-sync-in-go

Open Source
GO

env sync in go

No description provided on GitHub.

Created
Feb 2026
Last Updated
Feb 2026
Stars
0 ⭐
Status
Available

env-sync

Detect and fix drift between .env and .env.example files —


The FAFO

I build this stuff jst for fun, i'm bored by doing Machine learning(feature eng, optimization ) stuff, so i tried this and lock in for 3-4 hrs, doing FAFO with docs and blogs and built in one gooo....

Demo

$ env-sync check

Comparing .env → .env.example

  MISSING IN .env.example (3 keys — your teammates won't have these):
  āœ— STRIPE_WEBHOOK_SECRET
  āœ— ENABLE_BETA
  āœ— MAX_CONNECTIONS

  MISSING IN .env (2 keys — you might be missing required config):
  āœ— LEGACY_API_URL
  āœ— OLD_SECRET

  EMPTY VALUES in .env.example (2 keys):
  ⚠ DATABASE_URL
  ⚠ JWT_SECRET

  Matched: 14 keys āœ“

Run `env-sync fix` to auto-update .env.example

Installation

go install github.com/satyammistari/env-sync@latest

Or build from source:

git clone https://github.com/satyammistari/env-sync
cd env-sync
go build -o env-sync.exe .

Usage

Check for drift

# Default: compares .env → .env.example
env-sync check

# Custom paths
env-sync check --source .env.production --target .env.staging

# CI mode — exits code 1 on any drift, plain output, no color
env-sync check --ci

# Strict mode — also flag empty values in .env as errors
env-sync check --strict

# Ignore specific keys
env-sync check --ignore "LEGACY_KEY,DEPRECATED_SECRET"

Auto-fix your .env.example

# Preview what would change (always run this first)
env-sync fix --dry-run

# Apply the fix — adds missing keys with smart placeholders
env-sync fix

# Fix without creating a backup file
env-sync fix --backup=false

Example dry-run output:

Would add 3 keys to .env.example:

+ STRIPE_WEBHOOK_SECRET=your-api-secret-here
+ ENABLE_BETA=false
+ MAX_CONNECTIONS=100

Run without --dry-run to apply changes.

Visual diff

# Git-style diff in the terminal
env-sync diff

# Machine-readable JSON output (great for scripts)
env-sync diff --format json

# ASCII table format
env-sync diff --format table

CI/CD Integration

Add this step to your GitHub Actions workflow to catch drift before it ships:

- name: Check env drift
  run: env-sync check --ci

This exits with code 1 if any keys are missing from .env.example, blocking the merge automatically.


Why Go over Rust?

env-sync is a pure file I/O tool — it reads two small text files and compares their keys. There is no CPU-intensive computation, no parallel processing of gigabytes of data, no need for zero-cost abstractions. The performance bottleneck simply doesn't exist here.

Go was the right choice for three concrete reasons: it compiles to a single self-contained binary that anyone can go install in one command, its cobra ecosystem is the gold standard for CLI structure (used by kubectl and the GitHub CLI), and its standard library handles file I/O and string processing cleanly without reaching for external crates. I use Rust where it actually matters — log-anonymizer uses rayon for parallel processing of multi-GB log files where the performance difference is measurable and real. Choosing the right tool for the right job is the trade-off.


Architecture

env-sync/
ā”œā”€ā”€ cmd/                  # CLI commands (check, fix, diff, root)
ā”œā”€ā”€ internal/
│   ā”œā”€ā”€ parser/           # .env file parsing — isolated, tested independently
│   ā”œā”€ā”€ differ/           # Diff engine — pure logic, no I/O, no output
│   ā”œā”€ā”€ fixer/            # Placeholder suggestion engine + file writing
│   └── reporter/         # All terminal output — colors, formatting, CI mode
ā”œā”€ā”€ testdata/             # Sample .env files for integration tests
└── main.go

Each concern lives in its own package under internal/ — meaning nothing outside this module can import them. The parser knows nothing about output formatting. The differ knows nothing about how files are read. The reporter knows nothing about business logic. This separation means every package can be tested in complete isolation, and changing the output format never touches the parsing logic.


Engineering Trade-offs

1. Regex-free parser over regex for correctness Parsing .env files with regex breaks on edge cases: quoted values with spaces, inline comments, backslash-escaped characters, and the export KEY=value prefix pattern. I chose a character-by-character state machine parser instead. It's ~40 more lines of code but handles every real-world .env format correctly. Correctness over brevity.

2. Alphabetically sorted diff output over file-order output When you have 30 missing keys, seeing them in random insertion order is harder to scan than alphabetical order. The differ sorts all output slices before returning them. This is a small product decision that meaningfully improves readability at the cost of a trivial sort operation.

3. Never copy real values — only suggest placeholders The fix command could copy actual values from .env into .env.example to make onboarding faster. I deliberately chose not to do this. .env.example files get committed to git. Copying real values — even accidentally — is a secret leak. The suggestion engine generates safe placeholders based on key name patterns instead.


Flags Reference

FlagCommandDefaultDescription
--sourceall.envPath to source file
--targetall.env.examplePath to target file
--no-colorallfalseDisable colored output
--cicheckfalseCI mode, exit 1 on drift
--strictcheckfalseTreat empty values as errors
--ignorecheck""Comma-separated keys to ignore
--dry-runfixfalsePreview without writing
--suggestfixtrueSmart placeholder suggestions
--backupfixtrueBackup before modifying
--formatdiffprettyOutput format: pretty/json/table

Exit Codes

CodeMeaning
0No drift — all keys in sync
1Drift found — keys missing or empty
2Error — file not found, parse failure

Contributing

Pull requests are welcome. For major changes, open an issue first to discuss what you'd like to change. Make sure all tests pass before submitting:

go test ./... -v -race

Other Projects