Metadata-Version: 2.4
Name: envcompare
Version: 0.1.0
Summary: Zero-config .env file comparison - catch missing keys before production
Project-URL: Homepage, https://github.com/0xProgress/envcompare
Project-URL: Repository, https://github.com/0xProgress/envcompare.git
Project-URL: Issues, https://github.com/0xProgress/envcompare/issues
Author-email: 0xProgress <progressuwhuseba@gmail.com>
License: MIT
License-File: LICENSE
Keywords: ci,configuration,deployment,devops,dotenv,env,environment,environment-variables,testing
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.8
Requires-Dist: python-dotenv>=1.0.0
Provides-Extra: dev
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: twine>=4.0; extra == 'dev'
Description-Content-Type: text/markdown

# envcompare

Compare `.env` files in one command. Zero config. Security-first.

[![PyPI version](https://badge.fury.io/py/envcompare.svg)](https://badge.fury.io/py/envcompare)
[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## The Problem

`.env.example` says your app needs 12 keys. Your staging `.env` has 9. Production has 7, and someone changed `PORT` to a string. This breaks at 3 AM.

```bash
# .env.example          # .env.production (oops)
DATABASE_URL=...        DATABASE_URL=postgres://prod/db
SECRET_KEY=...          # ← MISSING - will crash
REDIS_URL=...           # ← MISSING - will crash
PORT=8080               PORT="8080"  # ← quoted, different type
```

## The Solution

```bash
$ uvx envcompare .env.example .env.production

❌ Missing in .env.production:
   SECRET_KEY
   REDIS_URL

🔧 Type mismatch:
   PORT: expected int (***), found string (***)
```

**Values are masked by default.** No secrets in your terminal or CI logs. Add `--show-values` if you need to see them.

## Why envcompare?

| | envcompare | dotenv-linter | Manual grep |
|---|---|---|---|
| Config required | ❌ None | ✅ YAML file | ❌ None |
| Catches missing keys | ✅ | ✅ | ❌ |
| Type checking | ✅ | ❌ | ❌ |
| Values masked by default | ✅ | ❌ | ❌ |
| CI-ready (JSON + exit codes) | ✅ | ❌ | ❌ |
| Zero install | ✅ `uvx` | ❌ | ✅ |

## Quick Start

```bash
# No install needed
uvx envcompare .env.example .env.production

# Or install once
pipx install envcompare
envcompare .env.example .env

# Or pip
pip install envcompare
envcompare .env.example
```

## Usage

### Everyday

```bash
# Compare with default .env
envcompare .env.example

# Compare two specific files
envcompare .env.example .env.staging

# Skip type checking (only missing keys)
envcompare .env.example --missing-only

# Ignore noisy keys
envcompare .env.example --ignore NODE_ENV --ignore LOG_LEVEL
```

### CI/CD

```bash
# Fail on missing keys, JSON output
envcompare .env.example .env.production --exit-code --format json

# Quiet mode - only missing keys, no extra warnings
envcompare .env.example .env.production --exit-code --quiet --missing-only
```

### Security

```bash
# Default: values masked
$ envcompare .env.example .env.production
⚠️  Extra in .env.production:
   SECRET_KEY=***SENSITIVE***
   PORT=***present***

# With --show-values: values visible (be careful!)
$ envcompare .env.example .env.production --show-values
⚠️  Extra in .env.production:
   SECRET_KEY=sk-live-abc123
   PORT=8080

# JSON always shows values (for programmatic use)
$ envcompare .env.example .env.production --format json
```

## All Flags

```
usage: envcompare [-h] [--version] [--exit-code] [--quiet]
               [--format {text,json}] [--ignore KEY]
               [--missing-only] [--show-values]
               example [target]

positional arguments:
  example         Example .env file (usually .env.example)
  target          Target file to compare (default: .env)

options:
  --version, -V   Show version and exit
  --exit-code     Exit 1 if issues found (for CI)
  --quiet, -q     Suppress "extra keys" warnings
  --format        text (default) or json
  --ignore KEY    Skip a key (repeatable)
  --missing-only  Only check for missing keys
  --show-values   Show actual values in output
```

## What envcompare checks

| Check | What it catches | Example |
|-------|----------------|---------|
| **Missing keys** | Keys in example but not in target | `SECRET_KEY` exists in `.env.example` but not `.env` |
| **Extra keys** | Keys in target but not in example | `DEBUG=true` in `.env` but not in template (warning only) |
| **Type mismatches** | Value changed type | `PORT=8080` vs `PORT="8080"` (int vs string) |

**Type checking is heuristic, not semantic.** It catches `8080`→`"8080"` but won't catch `false`→`true` (both are booleans) or `localhost`→`malicious-host` (both are strings). It's a helpful signal, not a security audit. Use `--missing-only` if you find it noisy.

## Exit Codes

| Code | Meaning |
|------|---------|
| 0 | Clean - all keys present, types match |
| 1 | Issues found - missing keys or type mismatches |
| 2 | Error - file not found or unreadable |

## CI Integration

### GitHub Actions
```yaml
- name: Verify env files
  run: |
    pip install envcompare
    envcompare .env.example .env.production --exit-code --format json
```

### GitLab CI
```yaml
check-env:
  script:
    - pip install envcompare
    - envcompare .env.example .env.staging --exit-code --quiet --missing-only
```

### Pre-commit
```yaml
repos:
  - repo: local
    hooks:
      - id: envcompare
        name: Check .env files
        entry: envcompare .env.example --exit-code
        language: system
        files: \.env$
```

## Security

- **Values masked by default** - No accidental secret exposure in terminals or CI logs
- **Sensitive keys detected** - Keys containing `SECRET`, `TOKEN`, `PASSWORD`, `KEY`, etc. show `***SENSITIVE***` instead of masked values
- **JSON exposes values** - `--format json` shows actual values. This is intentional for CI tooling that needs to parse output. Use only in secure CI environments.

**Masking is heuristic, not cryptographic.** Value prefixes (like `sk_live_` for Stripe) may be visible. This is a terminal display tool, not a redaction engine. Don't pipe output to untrusted systems.

## Known Trade-offs

| Trade-off | Why |
|-----------|-----|
| Type checking is on by default | Catches real config issues; use `--missing-only` if noisy |
| `KEY` substring matches `MONKEY` | Over-masking is safer than under-masking |
| Value prefixes may be visible | 4-char mask reveals `sk_***ef` for Stripe keys |
| JSON always shows values | CI tooling needs real values to parse |

## Philosophy

- **Zero config** - No `.envcomparerc`, no YAML, no JSON. CLI flags only.
- **Security-first defaults** - Values masked. Secrets protected. Explicit opt-in to see them.
- **One thing well** - Compare env files. Not a linter, not a validator, not a manager.
- **CI-native** - Exit codes, JSON output, quiet mode. Works in pipelines.
- **Honest about limitations** - Type checking is heuristic. Masking is best-effort. Documented, not hidden.

## Development

```bash
git clone https://github.com/0xProgress/envcompare.git
cd envcompare
pip install -e ".[dev]"
pytest            # 75 tests
pytest --cov      # with coverage
```

## License

Licensed under [MIT](/LICENSE)

## Contributing

Issues and PRs welcome. Before adding features:
- Does it require a config file? → No.
- Does it work in CI? → Must support exit codes and JSON.
- Does it keep the tool under 500 lines? → If not, it better be worth it.
- For more info click [here](/CONTRIBUTING.md)