Metadata-Version: 2.4
Name: arun-envdoctor
Version: 0.1.2
Summary: Local-first consistency checker for environment variables (native Python port)
Author: Arun Natesan
License: MIT
Project-URL: Homepage, https://github.com/arun-skg/envdoctor
Project-URL: Repository, https://github.com/arun-skg/envdoctor
Keywords: env,dotenv,environment-variables,cli,linter,static-analysis
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Environment :: Console
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# envdoctor (Python)

Native Python port of [envdoctor](https://github.com/arun-skg/envdoctor) — a
local-first consistency checker for environment variables, distributed on PyPI
so Python projects can use it without Node.

## Install

```bash
pip install arun-envdoctor
```

> The PyPI **distribution** is named `arun-envdoctor` (PyPI blocks `envdoctor` as
> too similar to an existing project), but the installed **command** and the
> importable **package** are both still `envdoctor`.

## Quick start

```bash
envdoctor scan --dir .        # audit; exit 1 on errors
envdoctor scan --strict       # treat warnings as errors too
envdoctor scan --json         # emit findings as a JSON array (no values)
```

## What it detects

Reconciles the environment variables **used** in your Python source
(`os.getenv("X")`, `os.environ.get("X")`, `os.environ["X"]`, and the
`from os import environ` forms) against those **defined** in your `.env` files,
then reports:

| Rule | Severity | Meaning |
|------|----------|---------|
| `undefined-in-source` | error | Referenced (source or infra files) but not defined in any `.env` file |
| `duplicates` | error | The same key is defined 2+ times within a single `.env` file |
| `public-prefix` | error | A secret-looking variable is exposed to client bundles via a public prefix (`NEXT_PUBLIC_`, `VITE_`, `REACT_APP_`, `EXPO_PUBLIC_`, `GATSBY_`, `NUXT_PUBLIC_`, `VUE_APP_`, `PUBLIC_`) |
| `type-mismatch` | error | A variable's value has incompatible inferred types across environments (e.g. `PORT=3000` vs `PORT=abc`) |
| `unused` | warning | Defined in `.env` but never referenced in source |
| `environment-diff` | warning | Defined in some environment files but missing from others |
| `weak-secret` | warning | A secret-looking variable has a weak, empty, or placeholder value |
| `typo` | warning | A used-but-undefined name closely matches a defined one (likely a typo) |

In addition to Python source, envdoctor scans **Docker Compose**
(`docker-compose.yml` / `compose.yaml`), **GitHub Actions** workflows
(`.github/workflows/*.yml`), and **Kubernetes** manifests (any YAML with both
`apiVersion:` and `kind:`) for referenced variables. Detection is dependency-free
(regex only, no YAML parser): shell-style interpolation `${VAR}` / `$VAR`
(including `${VAR:-default}` forms) across all three, plus
`${{ secrets.X }}`, `${{ vars.X }}`, and `${{ env.X }}` references in Actions.
These references feed the same missing/undefined and unused detectors, so a
variable referenced only in infra files but never defined is flagged, and one
defined and referenced only in infra is not reported unused.

Comments and docstrings are stripped before scanning, so documented examples
don't cause false positives. Nothing is uploaded and variable **values** are
never printed — they are used only for detection and never appear in any output
(human or `--json`). `envdoctor scan` exits `1` when there are errors (or with
`--strict`, warnings), making it CI-friendly. Pass `--json` to emit a JSON array
of findings (each with `rule`, `severity`, `name`, `message`, `file`, `line`)
for machine consumption.

## Library use

```python
from pathlib import Path
from envdoctor import scan

result = scan(Path("."))
for finding in result.errors:
    print(finding.name, finding.message)
```

## Development

```bash
pip install -e ".[dev]" pytest
pytest
```

## Subcommands

Alongside `scan`, every port shares these environment subcommands:

```bash
envdoctor diff <envA> <envB>       # compare two environments (add --json)
envdoctor sync <from> <to>         # copy missing keys (add --dry-run)
envdoctor init [--force]           # generate .env.example + ENVIRONMENT.md
envdoctor fix                      # (re)generate both docs
```

`diff` reports which variable names are only in one environment; `sync` appends
the missing keys to the target `.env` file as empty `KEY=` placeholders — values
are never copied.

### init / fix

Both commands generate two files at the project root from the union of every
variable name (defined in any `.env*` file ∪ referenced in source/infra), sorted
ascending. Values are **never** written.

- `.env.example` — a header comment followed by one `NAME=` line per variable.
- `ENVIRONMENT.md` — a Markdown table of each variable with `Defined`/`Used` columns.

`init` writes each file only if it does not already exist (`--force` overwrites);
`fix` always regenerates both. Every port produces byte-identical files.

## Schema validation

Add an `envdoctor.schema.json` at your project root to validate `.env` values:

```json
{
  "PORT":  { "type": "integer", "min": 1, "max": 65535 },
  "LEVEL": { "enum": ["debug", "info", "warn", "error"] },
  "TOKEN": { "type": "string", "optional": true }
}
```

Supported rule fields: `type` (string/integer/float/boolean/url/json), `enum`,
`regex`, `min`, `max`, `optional`. Values that fail are reported as
`schema-validation` errors (values are never printed).

## Other languages

envdoctor ships as a standalone native port for each ecosystem:

- [Node (reference)](..) · [Go](../go) · [Ruby](../ruby) · [PHP](../php) · [Java](../java) · [Perl](../perl)
- 📖 Docs: [arun-skg.github.io/envdoctor](https://arun-skg.github.io/envdoctor/)
- Main repository: [github.com/arun-skg/envdoctor](https://github.com/arun-skg/envdoctor)
