{"_id":"@arunskg/envdoctor","_rev":"4-eaf24dfc4b08212d01611e187fdce044","name":"@arunskg/envdoctor","dist-tags":{"latest":"1.0.0"},"versions":{"0.1.0":{"name":"@arunskg/envdoctor","version":"0.1.0","keywords":["env","environment","cli","dotenv","audit","lint","ci"],"author":{"name":"arun"},"license":"MIT","_id":"@arunskg/envdoctor@0.1.0","maintainers":[{"name":"arunskg","email":"arunskg12@gmail.com"}],"homepage":"https://github.com/arun-skg/envdoctor#readme","bugs":{"url":"https://github.com/arun-skg/envdoctor/issues"},"bin":{"envdoctor":"dist/index.js"},"dist":{"shasum":"0b2af3876630f20c6d332e509ef3fd14c0c7074e","tarball":"https://registry.npmjs.org/@arunskg/envdoctor/-/envdoctor-0.1.0.tgz","fileCount":5,"integrity":"sha512-PcU67+i9ZVrTn7udVy6usLLgH62kgIX+Cdd1nd8kJ8Ju74HBXNig267WkdApiGNceuhDrwgRvqZ5hV7ZQ4lrFA==","signatures":[{"sig":"MEYCIQCa+lawjJ9k5RTBfrek2Ldv4CEu3dTSQ8EoheP7cnwpfAIhAMBhzxxVkqtNfcOkgZoK4r6ErInVqHvIhE8tMEYY+KAE","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":168050},"type":"module","engines":{"node":">=18"},"exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"263d13d86c90c5f163d185211256400ab18a7104","scripts":{"dev":"tsup --watch","lint":"eslint .","test":"vitest run","build":"tsup","format":"prettier --write .","lint:fix":"eslint . --fix","typecheck":"tsc --noEmit","test:watch":"vitest","format:check":"prettier --check .","prepublishOnly":"npm run build"},"_npmUser":{"name":"arunskg","email":"arunskg12@gmail.com"},"repository":{"url":"git+https://github.com/arun-skg/envdoctor.git","type":"git"},"_npmVersion":"10.9.0","description":"Local-first consistency checker for environment variables. Detects missing, unused, duplicate, and mismatched variables across .env files, Docker Compose, GitHub Actions, and source code.","directories":{},"_nodeVersion":"22.11.0","dependencies":{"zod":"^3.23.8","yaml":"^2.5.0","chalk":"^5.3.0","dotenv":"^16.4.5","commander":"^12.1.0","fast-glob":"^3.3.2"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.2.4","eslint":"^9.9.0","vitest":"^2.0.5","prettier":"^3.3.3","@eslint/js":"^9.9.0","typescript":"^5.5.4","@types/node":"^20.14.0","typescript-eslint":"^8.2.0"},"_npmOperationalInternal":{"tmp":"tmp/envdoctor_0.1.0_1786868574155_0.7557659945771531","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@arunskg/envdoctor","version":"0.1.1","keywords":["env","environment","cli","dotenv","audit","lint","ci"],"author":{"name":"arun"},"license":"MIT","_id":"@arunskg/envdoctor@0.1.1","maintainers":[{"name":"arunskg","email":"arunskg12@gmail.com"}],"homepage":"https://github.com/arun-skg/envdoctor#readme","bugs":{"url":"https://github.com/arun-skg/envdoctor/issues"},"bin":{"envdoctor":"dist/index.js"},"dist":{"shasum":"edd626370ff90c6d53ecb6d1e4ffe398f9ba8875","tarball":"https://registry.npmjs.org/@arunskg/envdoctor/-/envdoctor-0.1.1.tgz","fileCount":5,"integrity":"sha512-dZLisCpDtodEkjV2u++nfSC2c0SW0SPLcVZEv6a/szMRxhA4gq9OFyBPIo55v1JB8ODBrLwX2O8GZMMLXCKFlQ==","signatures":[{"sig":"MEUCIQChmMlvmAW7W3MC7q537Cg+FtEwZSuNjZ8XPFLeUXTbaQIgb3MLuHwUB+ko8aJIHuW6I2fwmUdoTuDYsV66t8enflc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arunskg%2fenvdoctor@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":168796},"type":"module","engines":{"node":">=18"},"exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"db7d9e387758daff03fc17bcc295233f81a4d8e8","scripts":{"dev":"tsup --watch","lint":"eslint .","test":"vitest run","build":"tsup","format":"prettier --write .","lint:fix":"eslint . --fix","typecheck":"tsc --noEmit","test:watch":"vitest","format:check":"prettier --check .","prepublishOnly":"npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:02781b50-d659-4df7-85a7-8c5b2e05c07f"}},"repository":{"url":"git+https://github.com/arun-skg/envdoctor.git","type":"git"},"_npmVersion":"11.17.0","description":"Local-first consistency checker for environment variables. Detects missing, unused, duplicate, and mismatched variables across .env files, Docker Compose, GitHub Actions, and source code.","directories":{},"_nodeVersion":"24.19.0","dependencies":{"zod":"^3.23.8","yaml":"^2.5.0","chalk":"^5.3.0","dotenv":"^16.4.5","commander":"^12.1.0","fast-glob":"^3.3.2"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.2.4","eslint":"^9.9.0","vitest":"^2.0.5","prettier":"^3.3.3","@eslint/js":"^9.9.0","typescript":"^5.5.4","@types/node":"^20.14.0","typescript-eslint":"^8.2.0"},"_npmOperationalInternal":{"tmp":"tmp/envdoctor_0.1.1_1786869375280_0.07971563121614023","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@arunskg/envdoctor","version":"0.1.2","keywords":["env","environment","cli","dotenv","audit","lint","ci"],"author":{"name":"arun"},"license":"MIT","_id":"@arunskg/envdoctor@0.1.2","maintainers":[{"name":"arunskg","email":"arunskg12@gmail.com"}],"homepage":"https://github.com/arun-skg/envdoctor#readme","bugs":{"url":"https://github.com/arun-skg/envdoctor/issues"},"bin":{"envdoctor":"dist/index.js"},"dist":{"shasum":"1074bce94d156aecc38781bf355897801f11d25f","tarball":"https://registry.npmjs.org/@arunskg/envdoctor/-/envdoctor-0.1.2.tgz","fileCount":5,"integrity":"sha512-S36audRm0UK4eYdJ/ThRKZ6bU0x8NuLySU+1NPHarBN5w50n0QNlVYr6nDOThVK8Zq5qR6AgFpkZCppnZZZ2lw==","signatures":[{"sig":"MEUCICgjl5NOBh+6gqku3c6DyBsKGclQILLxOa47BshDjMz3AiEAkf6YoGf4ZxP2qiMMog8id01XJST5Cx7SaHHvYc9+zPs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arunskg%2fenvdoctor@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":207610},"type":"module","engines":{"node":">=18"},"exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"e987e44c13372f4fe0b710ab0f40d01859b4cdd8","scripts":{"dev":"tsup --watch","lint":"eslint .","test":"vitest run","build":"tsup","format":"prettier --write .","lint:fix":"eslint . --fix","typecheck":"tsc --noEmit","test:watch":"vitest","format:check":"prettier --check .","prepublishOnly":"npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:02781b50-d659-4df7-85a7-8c5b2e05c07f"}},"repository":{"url":"git+https://github.com/arun-skg/envdoctor.git","type":"git"},"_npmVersion":"11.17.0","description":"Local-first consistency checker for environment variables. Detects missing, unused, duplicate, and mismatched variables across .env files, Docker Compose, GitHub Actions, and source code.","directories":{},"_nodeVersion":"24.19.0","dependencies":{"zod":"^3.23.8","yaml":"^2.5.0","chalk":"^5.3.0","dotenv":"^16.4.5","commander":"^12.1.0","fast-glob":"^3.3.2"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.2.4","eslint":"^9.9.0","vitest":"^2.0.5","prettier":"^3.3.3","@eslint/js":"^9.9.0","typescript":"^5.5.4","@types/node":"^20.14.0","typescript-eslint":"^8.2.0"},"_npmOperationalInternal":{"tmp":"tmp/envdoctor_0.1.2_1786871066351_0.09544112012087647","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"_id":"@arunskg/envdoctor@1.0.0","bin":{"envdoctor":"dist/index.js"},"bugs":{"url":"https://github.com/arun-skg/envdoctor/issues"},"dist":{"shasum":"780059e5e7dda63a3e2fe351b3893b36be5c66e1","tarball":"https://registry.npmjs.org/@arunskg/envdoctor/-/envdoctor-1.0.0.tgz","fileCount":5,"integrity":"sha512-JM/inECbKSPIdYIFZaD4HzQuHZ+Q8/TMOp+ARku+hoSKReFCFohJgBLXnKVvWFo9y/nV1guaNxmTaZfuxuBeWg==","signatures":[{"sig":"MEUCIG2Z+euK7dvDr46Uf6fpM0E+KtN2XNiU+RYzZBvdmVNpAiEAidEvx+9cTXo89D3diq8MXs8PaJThFXPLxW2ZEZnYnds=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCVdr38x3WFqlMp6TMufNbg3l3yLWbAAdXC/7924LA9cQIgCH/IuL8hnrWRDCINTrA7AoUqQ53IAbLR3P/C0Vr8Zho="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@arunskg%2fenvdoctor@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":249108},"name":"@arunskg/envdoctor","type":"module","author":{"name":"arun"},"engines":{"node":">=18"},"exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"5f1374eed08b7e1b85f86dca9da2d55687cdb9c6","license":"MIT","scripts":{"dev":"tsup --watch","lint":"eslint .","test":"vitest run","build":"tsup","format":"prettier --write .","lint:fix":"eslint . --fix","typecheck":"tsc --noEmit","test:watch":"vitest","format:check":"prettier --check .","prepublishOnly":"npm run build"},"version":"1.0.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"02781b50-d659-4df7-85a7-8c5b2e05c07f"}},"homepage":"https://github.com/arun-skg/envdoctor#readme","keywords":["env","environment","cli","dotenv","audit","lint","ci","envdoctor","environment-variables","kubernetes","docker-compose","github-actions","secrets","devops","ci-cd","nextjs","vite","static-analysis"],"repository":{"url":"git+https://github.com/arun-skg/envdoctor.git","type":"git"},"_npmVersion":"11.19.0","description":"The ESLint for environment variables — catches missing, unused, duplicate, and leaked config across .env files, source code, Docker Compose, Kubernetes, and GitHub Actions. Local-first: no network, no telemetry.","directories":{},"maintainers":[{"name":"arunskg","email":"arunskg12@gmail.com"}],"_nodeVersion":"24.21.0","dependencies":{"zod":"^4.4.3","yaml":"^2.5.0","chalk":"^6.0.0","dotenv":"^17.4.2","commander":"^15.0.0","fast-glob":"^3.3.2"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.2.4","eslint":"^10.9.1","vitest":"^4.1.11","prettier":"^3.3.3","@eslint/js":"^10.0.1","typescript":"^5.9.3","@types/node":"^26.4.0","typescript-eslint":"^8.68.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/envdoctor_1.0.0_1790757319265_0.5386289170032645"}}},"time":{"created":"2026-08-16T08:22:53.953Z","modified":"2026-09-30T08:35:19.766Z","0.1.0":"2026-08-16T08:22:54.306Z","0.1.1":"2026-08-16T08:36:15.421Z","0.1.2":"2026-08-16T09:04:26.498Z","1.0.0":"2026-09-30T08:35:19.400Z"},"bugs":{"url":"https://github.com/arun-skg/envdoctor/issues"},"author":{"name":"arun"},"license":"MIT","homepage":"https://github.com/arun-skg/envdoctor#readme","keywords":["env","environment","cli","dotenv","audit","lint","ci","envdoctor","environment-variables","kubernetes","docker-compose","github-actions","secrets","devops","ci-cd","nextjs","vite","static-analysis"],"repository":{"url":"git+https://github.com/arun-skg/envdoctor.git","type":"git"},"description":"The ESLint for environment variables — catches missing, unused, duplicate, and leaked config across .env files, source code, Docker Compose, Kubernetes, and GitHub Actions. Local-first: no network, no telemetry.","maintainers":[{"name":"arunskg","email":"arunskg12@gmail.com"}],"readme":"<p align=\"center\">\n  <img src=\"docs/assets/logo.svg\" width=\"120\" alt=\"envdoctor logo\" />\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.producthunt.com/products/envdoctor?embed=true&amp;utm_source=badge-featured&amp;utm_medium=badge&amp;utm_campaign=badge-envdoctor\" target=\"_blank\" rel=\"noopener noreferrer\"><img src=\"https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1229915&amp;theme=light&amp;t=1787493130629\" alt=\"envdoctor - ESLint for env vars — catch config bugs before they deploy | Product Hunt\" width=\"250\" height=\"54\" /></a>\n</p>\n\n# @arunskg/envdoctor\n\n[![npm version](https://img.shields.io/npm/v/@arunskg/envdoctor.svg)](https://www.npmjs.com/package/@arunskg/envdoctor)\n[![npm downloads](https://img.shields.io/npm/dm/@arunskg/envdoctor.svg)](https://www.npmjs.com/package/@arunskg/envdoctor)\n[![total downloads](https://img.shields.io/npm/dt/@arunskg/envdoctor.svg?label=downloads%20total)](https://www.npmjs.com/package/@arunskg/envdoctor)\n[![CI](https://github.com/arun-skg/envdoctor/actions/workflows/ci.yml/badge.svg)](https://github.com/arun-skg/envdoctor/actions/workflows/ci.yml)\n[![node](https://img.shields.io/node/v/@arunskg/envdoctor.svg)](https://nodejs.org)\n[![license](https://img.shields.io/npm/l/@arunskg/envdoctor.svg)](./LICENSE)\n[![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](./CONTRIBUTING.md)\n[![Code of Conduct](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](https://github.com/arun-skg/envdoctor?tab=coc-ov-file)\n\n[![PyPI](https://img.shields.io/pypi/v/arun-envdoctor.svg?label=PyPI&logo=pypi&logoColor=white)](https://pypi.org/project/arun-envdoctor/)\n[![Gem](https://img.shields.io/gem/v/envdoctor.svg?label=RubyGems&logo=rubygems&logoColor=white)](https://rubygems.org/gems/envdoctor)\n[![Packagist](https://img.shields.io/packagist/v/arun-skg/envdoctor.svg?label=Packagist&logo=packagist&logoColor=white)](https://packagist.org/packages/arun-skg/envdoctor)\n[![Maven Central](https://img.shields.io/maven-central/v/io.github.arun-skg/envdoctor?label=Maven%20Central&color=C71A36&logo=apachemaven&logoColor=white)](https://central.sonatype.com/artifact/io.github.arun-skg/envdoctor)\n[![Go module](https://img.shields.io/github/v/tag/arun-skg/envdoctor?filter=go/*&label=Go&color=00ADD8&logo=go&logoColor=white)](https://pkg.go.dev/github.com/arun-skg/envdoctor/go)\n[![crates.io](https://img.shields.io/crates/v/arun-envdoctor.svg?label=crates.io&logo=rust&logoColor=white)](https://crates.io/crates/arun-envdoctor)\n[![CPAN](https://img.shields.io/cpan/v/App-Envdoctor.svg?label=CPAN&logo=perl&logoColor=white)](https://metacpan.org/dist/App-Envdoctor)\n[![NuGet](https://img.shields.io/nuget/v/envdoctor.svg?label=NuGet&logo=nuget&logoColor=white)](https://www.nuget.org/packages/envdoctor)\n[![Swift](https://img.shields.io/badge/Swift-SwiftPM-F05138?logo=swift&logoColor=white)](./swift)\n\n**The ESLint for environment variables.** envdoctor audits every place your config lives — `.env` files, source code, Docker Compose, Kubernetes manifests, and GitHub Actions — and fails your build *before* a missing key, a dead variable, or a `NEXT_PUBLIC_` secret leak fails your deploy.\n\n```bash\nnpx @arunskg/envdoctor scan     # Node — or pip / gem / composer / go install, see below\n```\n\nRuns **completely locally**: no network calls, no telemetry, variable values never printed. Available as [native ports](#native-ports) for **Node, Python, Go, Rust, Ruby, PHP, Perl, and Java** — each installable from its own package manager.\n\n📖 **Documentation: [arun-skg.github.io/envdoctor](https://arun-skg.github.io/envdoctor/)** — full guides, per-language references, and examples.\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│  ENVIRONMENT AUDIT                                              │\n│  ════════════════════════════════════════════════════════════════│\n│                                                                 │\n│  Missing (error)                                                │\n│  ─────────────────────────────────────────────────────────────  │\n│  ❌  COMPOSE_ONLY       docker-compose.yml:9   referenced but   │\n│                          not defined in any environment file   │\n│  ❌  NEW_FEATURE_FLAG   src/index.ts:5         used in source   │\n│                          code but not defined in any           │\n│                          environment file                      │\n│                                                                 │\n│  Unused (warning)                                               │\n│  ─────────────────────────────────────────────────────────────  │\n│  ⚠  DEBUG_MODE          .env:7               defined but never │\n│                          referenced anywhere                    │\n│                                                                 │\n│  Duplicates (error)                                             │\n│  ─────────────────────────────────────────────────────────────  │\n│  ❌  NODE_ENV            .env:2,12           defined 2 times   │\n│                                                                 │\n│  Type mismatch (error)                                          │\n│  ─────────────────────────────────────────────────────────────  │\n│  ❌  PORT                expected: integer · found: string      │\n│                                                                 │\n│  Summary: 8 files scanned · 15 variables · 4 errors · 16 warns │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n## Contents\n\n- [Why](#why)\n- [Why not X?](#why-not-x)\n- [Supported formats](#supported-formats)\n- [Installation](#installation)\n- [Native ports](#native-ports)\n- [Quick start](#quick-start)\n- [Help make envdoctor smarter](#-help-make-envdoctor-smarter)\n- [Detectors](#detectors)\n- [Runtime snapshots](#runtime-snapshots)\n- [Commands](#commands)\n- [Configuration](#configuration)\n- [Environment labels](#environment-labels)\n- [Output formats](#output-formats)\n- [CI integration](#ci-integration)\n- [Security](#security)\n- [Architecture](#architecture)\n- [Development](#development)\n- [Contributing](#contributing)\n- [Download trends](#download-trends)\n- [License](#license)\n\n## Why\n\nEnvironment drift is a silent class of bug: a variable is used in code but never\ndocumented, defined in `.env` but dead, present in `development` but forgotten in\n`production`, or a secret accidentally shipped to the client bundle behind a\n`NEXT_PUBLIC_` prefix. `envdoctor` reconciles every place a variable can appear —\n`.env` files, Docker Compose, Kubernetes manifests, GitHub Actions, and your\nsource code — into one normalized model, then runs a suite of detectors over it.\n\nIt is **local-first**: everything runs on your machine, nothing is uploaded, and\nvariable *values* are never printed or written into generated artifacts.\n\n## Why not X?\n\nenvdoctor checks the **consistency and hygiene of your env config, locally**. It\ndeliberately does not try to be a secrets store or a git-history scanner:\n\n| Tool | What it does | How envdoctor differs |\n|------|--------------|------------------------|\n| **dotenv-linter** | Lints the *syntax* of `.env` files (one ecosystem) | envdoctor reconciles `.env` **against your code, Compose, k8s, and CI** — cross-file, not per-file — with native ports for 7 languages |\n| **gitleaks / trufflehog / git-secrets** | Find leaked secret *values* in git history | envdoctor catches the *naming/config mistake* (e.g. a secret behind `VITE_`) **before** it ships — different job, good complement |\n| **Doppler / Infisical / dotenv-vault** | Hosted secrets storage and sync | envdoctor stores nothing and never touches the network — it audits the files you already have |\n| **checkov / kics** | General IaC security scanning | envdoctor is purpose-built for the env-variable layer, including source-code usage and framework prefixes |\n| **ESLint / language linters** | Catch bad code | They stop at `process.env.X` — envdoctor checks whether `X` is defined, typed, documented, and safe to expose |\n\n## Supported formats\n\n| Source | What is read |\n|--------|--------------|\n| dotenv | `.env`, `.env.local`, `.env.production`, `.env.*` |\n| Docker Compose | `environment:` keys and `${VAR}` interpolation |\n| Kubernetes | `env:`, `envFrom:`, ConfigMap/Secret manifests |\n| GitHub Actions | workflow `env:`, `secrets.*`, `vars.*` |\n| Source code | `process.env.X` / `import.meta.env.X` (`.ts/.tsx/.js/.jsx/.mjs/.cjs`) |\n\n## Installation\n\n```bash\n# From npm\nnpm install -g @arunskg/envdoctor\n\n# Or run directly with npx\nnpx @arunskg/envdoctor scan\n```\n\nUsing another language? See [native ports](#native-ports) below.\n\n## Native ports\n\nenvdoctor is published as a **standalone native implementation** for each\necosystem — no Node required, no wrappers. Every port scans its own language's\nenvironment idioms, reconciles them against your `.env` files, and exits `1` on\nerrors so it drops straight into CI.\n\n| Ecosystem | Install | Detects |\n|-----------|---------|---------|\n| **Node** (reference) | `npm install -g @arunskg/envdoctor` | `process.env.X`, `import.meta.env.X` |\n| **Deno** ([`jsr.json`](./jsr.json)) | `deno run -A jsr:@arunskg/envdoctor` | `process.env.X`, `import.meta.env.X` |\n| **Python** ([`python/`](./python)) | `pip install arun-envdoctor` | `os.getenv`, `os.environ[...]`, `os.environ.get` |\n| **Go** ([`go/`](./go)) | `go install github.com/arun-skg/envdoctor/go/cmd/envdoctor@latest` | `os.Getenv`, `os.LookupEnv` |\n| **Rust** ([`rust/`](./rust)) | `cargo install arun-envdoctor` | `std::env::var`, `env!`, `option_env!` |\n| **Ruby** ([`ruby/`](./ruby)) | `gem install envdoctor` | `ENV[\"X\"]`, `ENV.fetch(\"X\")` |\n| **PHP** ([`php/`](./php)) | `composer require --dev arun-skg/envdoctor` | `getenv`, `$_ENV`, `$_SERVER` |\n| **Java** ([`java/`](./java)) | [`io.github.arun-skg:envdoctor`](https://central.sonatype.com/artifact/io.github.arun-skg/envdoctor) | `System.getenv(\"X\")` |\n| **Perl** ([`perl/`](./perl)) | `cpanm App::Envdoctor` | `$ENV{X}` |\n| **.NET** ([`dotnet/`](./dotnet)) | `dotnet tool install -g envdoctor` | `Environment.GetEnvironmentVariable(\"X\")` |\n| **Swift** ([`swift/`](./swift)) | [SwiftPM — build from source](./swift#install) | `process.env.X`, `import.meta.env.X` |\n\nAll ports share the same CLI shape:\n\n```bash\nenvdoctor scan --dir .        # audit; exit 1 on errors\nenvdoctor scan --strict       # treat warnings as errors too\n```\n\n> **Note:** the Python distribution is named `arun-envdoctor` on PyPI (the bare\n> name is blocked as too similar to an existing project), but the installed\n> command and importable package are both `envdoctor`. The Deno row runs the\n> same TypeScript core as Node, published to [JSR](https://jsr.io/@arunskg/envdoctor).\n\nEach port has its own README, test suite, and CI workflow, and they are kept\n**behaviour-identical** — the same project produces byte-for-byte-equivalent\nfindings (and `--json` output) in every language. The native ports are now at\n**full feature parity** with the Node reference: all **ten detectors**, the\n`scan` / `diff` / `sync` / `init` / `fix` subcommands, `--json` output, and\nDocker Compose / Kubernetes / GitHub Actions scanning.\n\n| Detector | Node | Python · Go · Rust · Ruby · PHP · Perl · Java · .NET · Swift |\n|---|:---:|:---:|\n| missing / undefined-in-source | ✅ | ✅ |\n| unused | ✅ | ✅ |\n| duplicates | ✅ | ✅ |\n| public-prefix (secret leak) | ✅ | ✅ |\n| weak-secret | ✅ | ✅ |\n| typo (did-you-mean) | ✅ | ✅ |\n| environment-diff | ✅ | ✅ |\n| type-mismatch | ✅ | ✅ |\n| schema-validation | ✅ | ✅ |\n| `--json` output | ✅ | ✅ |\n| `scan` · `diff` · `sync` · `init` · `fix` | ✅ | ✅ |\n| Docker Compose · Kubernetes · GitHub Actions sources | ✅ | ✅ |\n\nSchema validation reads an `envdoctor.schema.json` at the project root, e.g.:\n\n```json\n{\n  \"PORT\":  { \"type\": \"integer\", \"min\": 1, \"max\": 65535 },\n  \"LEVEL\": { \"enum\": [\"debug\", \"info\", \"warn\", \"error\"] },\n  \"API\":   { \"type\": \"url\" },\n  \"TOKEN\": { \"type\": \"string\", \"optional\": true }\n}\n```\n\n> **Note:** run `npx @arunskg/envdoctor` from your project directory, not from\n> inside a checkout of this repo — npx resolves the local package first, whose\n> `envdoctor` bin isn't on your PATH, and you'll see `envdoctor: command not\n> found`. After a global install, the short `envdoctor` command works anywhere.\n\n## Quick start\n\n```bash\n# Bootstrap config + .env.example + ENVIRONMENT.md in your project\nenvdoctor init\n\n# Scan for issues (exits 1 on errors, 0 on clean)\nenvdoctor scan\n\n# Compare two environments\nenvdoctor diff development production\n\n# Copy missing keys from .env to .env.local\nenvdoctor sync development local\n\n# Scan only files changed on this branch\nenvdoctor scan --since HEAD\n\n# Generate/update docs (dry-run first)\nenvdoctor fix --dry-run\nenvdoctor fix\n```\n\n> **Adopting on a legacy project?** Snapshot today's findings and fail CI only\n> on *new* ones:\n>\n> ```bash\n> envdoctor scan --write-baseline .envdoctor-baseline.json\n> envdoctor scan --baseline .envdoctor-baseline.json   # in CI\n> ```\n\n## 🧪 Help make envdoctor smarter\n\nenvdoctor is young and its detectors are opinionated. If it **missed\nsomething**, **cried wolf**, or **doesn't understand your framework** (Rails?\nDjango? SvelteKit? Terraform?), that's a bug *in my priorities* — tell me:\n\n- 🐺 [Report a false positive](https://github.com/arun-skg/envdoctor/issues/new?template=false_positive.yml) — 30 seconds, no values needed\n- 🔍 [Report what it missed](https://github.com/arun-skg/envdoctor/issues/new?template=missing_support.yml) — a snippet is enough\n- 🗳️ [Vote on language/framework support](https://github.com/arun-skg/envdoctor/discussions) — thumbs-up decides the roadmap\n\nEvery report gets a human reply within 48 hours.\n\n## Detectors\n\n| Detector | Severity | What it catches |\n|----------|----------|-----------------|\n| **missing** | error | Variables referenced in Docker Compose (definitions + `${VAR}` interpolation), Kubernetes, GitHub Actions, or `.env.example` but not defined in any `.env` file |\n| **undefined-in-source** | error | `process.env.X` / `import.meta.env.X` in source code with no definition in any `.env` file and not in `.env.example` |\n| **unused** | warning | Variables defined in `.env` files but never referenced in source, compose, k8s, or actions |\n| **duplicates** | error/warning | Same key defined twice in one file (error); same key across files sharing one environment label (warning) |\n| **environment-diff** | warning | Set-membership diffs across environments (e.g. `dev` vs `prod`) |\n| **type-mismatch** | error | Incompatible inferred types across environments, or values failing their own inferred type |\n| **schema-validation** | error | A value does not match its declared `schema` rule in the config |\n| **public-prefix** | error | Secret-looking variable uses a public framework prefix and would be exposed to client bundles. Covers `NEXT_PUBLIC_` (Next.js), `VITE_` (Vite), `PUBLIC_` (SvelteKit, Astro), `REACT_APP_` (Create React App), `GATSBY_` (Gatsby), `NUXT_PUBLIC_` (Nuxt), `EXPO_PUBLIC_` (Expo) and `ASTRO_PUBLIC_` |\n| **weak-secret** | warning | Secret-like variable has a placeholder or very short value |\n| **typo** | warning | A referenced name closely matches a defined name and may be a typo |\n\nAny detector can be downgraded or disabled via the [`rules`](#configuration)\nconfig or an [inline ignore](#inline-ignores).\n\n## Runtime snapshots\n\nThe detectors above reconcile *files*. Runtime snapshots reconcile *machines* —\nthe classic \"builds on my laptop, fails on the server\" problem. `snapshot`\ncaptures a sanitized picture of the **live** shell runtime and diffs two of them:\n\n- **Tool versions** — `node`, `python`, `go`, `rustc`, `java`, `ruby`, `php`,\n  `perl`, `cc`, `git` (present tools only, with the `$PATH` directory each\n  resolved from)\n- **`$PATH` order** — precedence is significant, so reordering is reported even\n  when the set of directories is identical\n- **Global packages** — opt-in via `--globals` (currently npm; slower)\n- **OS / arch / release**\n- **Environment flag names** — **names only, never values**; secret-looking\n  names (`*_TOKEN`, `*_SECRET`, `*_PASSWORD`, …) are dropped entirely\n\nA snapshot serializes to a compact, paste-able token\n(`envd1:` + base64url-gzipped JSON) that is safe to drop into an issue or chat —\nthe redaction invariant means no value ever leaves your machine.\n\n```bash\n# On machine A — share a token, or write JSON\nenvdoctor snapshot --token > a.token\nenvdoctor snapshot -o a.snapshot.json --globals\n\n# On machine B — compare against A (token or file, either argument)\nenvdoctor snapshot-diff a.snapshot.json \"$(cat a.token)\"\n```\n\n`snapshot-diff` exits `0` when the runtimes are equivalent and `1` on drift, so\nit drops into CI exactly like `scan`. Differing *env flag names* are reported for\ncontext but do not, by themselves, count as drift.\n\n```\nRUNTIME DIFF\n────────────────────────\n\n  A → B\n\n  ✓ OS  darwin/arm64 25.6.0\n\n  Tools\n  ✓ go       1.26.0\n  ⚠ node     22.11.0 → 18.0.0\n  ✓ python3  3.14.6\n\n  PATH\n  ⚠ same entries, different order\n  ❌ only in A: ~/.local/bin\n\n  ✗ runtime drift detected\n```\n\n> Runtime snapshots are currently implemented in the Node reference only; the\n> [native ports](#native-ports) focus on the file detectors.\n\n## Commands\n\n### `envdoctor init [--force]`\n\nBootstraps a project:\n- Creates `envdoctor.config.ts` with commented defaults (if missing)\n- Generates `.env.example` from discovered variables (if missing)\n- Generates `ENVIRONMENT.md` documentation (if missing)\n\nNever overwrites existing files without `--force`.\n\n### `envdoctor scan [options]`\n\nRuns the full audit.\n\n| Option | Description |\n|--------|-------------|\n| `-d, --dir <path>` | Project root (default: cwd) |\n| `--strict` | Treat warnings as errors (exit 1) |\n| `--format <format>` | Output format: `human` (default), `json`, or `sarif` |\n| `--json` | Alias for `--format json` |\n| `--verbose` | Show file:line locations |\n| `--only <ruleId>` | Run only specific detector(s), comma-separated |\n| `--baseline <path>` | Suppress findings listed in a baseline file |\n| `--write-baseline <path>` | Write current findings to a baseline file |\n| `--staged` | Only scan files staged for commit |\n| `--since <ref>` | Only scan files changed since a git ref (e.g. `HEAD~1`) |\n\n**Exit codes:** `0` = clean, `1` = errors found, `2` = usage/config error\n\nThe `--baseline` / `--write-baseline` pair lets you adopt `envdoctor` on a legacy\nproject: snapshot today's findings, then fail CI only on *new* ones.\n\n`--staged` and `--since` are useful in pre-commit hooks and CI to audit only the\nfiles touched by a changeset instead of the whole repository.\n\n### `envdoctor fix [options]`\n\nGenerates/updates safe artifacts based on the audit:\n- `.env.example` — all known variables with placeholders (secrets get empty values)\n- `ENVIRONMENT.md` — reference table + per-environment sections\n- `.github/ENVIRONMENT.md` — checklist of `secrets.*`/`vars.*` for GitHub Actions (if applicable)\n- `env.d.ts` — TypeScript ambient declaration for `process.env` variables\n- `envdoctor.schema.ts` — inferred Zod-style validation schema from observed values\n  (e.g. integer ranges, enum sets). Import and merge it into `envdoctor.config.ts`\n  to enable the `schema-validation` detector.\n\n| Option | Description |\n|--------|-------------|\n| `--dry-run` | Preview changes without writing |\n| `--force` | Overwrite without confirmation |\n\n### `envdoctor diff <env1> <env2> [--json]`\n\nFocused comparison between two environments (e.g. `dev prod`, `development production`).\nShows per-variable status: `✓ same`, `⚠ different`, `❌ missing`.\n\n### `envdoctor sync <source> <target>`\n\nCopy missing variable *keys* from one environment file to another without\noverwriting existing values. Useful for keeping `.env.local` or `.env.production`\nup to date after adding variables to `.env`.\n\n```bash\n# Append keys that exist in .env but are missing from .env.local\nenvdoctor sync development local\n\n# Or by explicit file suffix\nenvdoctor sync .env .env.production\n```\n\nOnly keys are copied; values are left untouched so target-specific values and\nsecrets stay safe.\n\n## Configuration\n\nConfiguration is optional — defaults are sensible for most projects. Create\n`envdoctor.config.ts` (or `.js`/`.mjs`/`.cjs`, or an `envdoctor` key in\n`package.json`):\n\n```ts\nexport default {\n  // Glob patterns for dotenv files\n  envFilePatterns: [\".env\", \".env.*\"],\n\n  // Docker Compose file patterns\n  composeFilePatterns: [\"**/docker-compose*.y*ml\", \"**/compose*.y*ml\"],\n\n  // GitHub Actions workflow patterns\n  actionsFilePatterns: [\".github/workflows/**/*.y*ml\"],\n\n  // Kubernetes manifest patterns\n  k8sFilePatterns: [\"**/k8s/**/*.y*ml\", \"**/manifests/**/*.y*ml\"],\n\n  // Source file extensions to scan\n  sourceExtensions: [\"ts\", \"tsx\", \"js\", \"jsx\", \"mjs\", \"cjs\"],\n\n  // Variable names to ignore entirely (glob patterns, e.g. \"AWS_*\")\n  ignoreVariables: [],\n\n  // File paths to ignore (glob patterns)\n  ignoreFiles: [],\n\n  // Explicit environment label → file list overrides\n  environments: {\n    // development: [\".env\", \".env.local\"],\n    // production: [\".env.production\"],\n  },\n\n  // Fail the audit when only warnings are present\n  strict: false,\n\n  // Per-detector severity overrides: \"error\", \"warning\", or \"off\"\n  rules: {\n    // unused: \"off\",\n    // \"environment-diff\": \"error\",\n  },\n\n  // Per-variable value validation (feeds the schema-validation detector)\n  schema: {\n    // PORT: { type: \"integer\", min: 1024 },\n    // RATE: { type: \"float\", min: 0, max: 1 },\n    // NODE_ENV: { enum: [\"development\", \"production\", \"test\"] },\n    // API_URL: { type: \"url\" },\n    // FEATURE_FLAGS: { type: \"json\" },\n    // Optional variables are allowed to be empty/missing\n    // LOG_LEVEL: { type: \"string\", optional: true },\n  },\n};\n```\n\n### Inline ignores\n\nSuppress a detector for a specific variable with a comment on the preceding line:\n\n```env\n# envdoctor:ignore unused\nDEBUG_MODE=true\n\n# envdoctor:ignore unused, weak-secret\nMY_TOKEN=placeholder\n```\n\n## Environment labels\n\n| File | Label |\n|------|-------|\n| `.env` | `development` (base) |\n| `.env.local` | `local` |\n| `.env.production` | `production` |\n| `.env.<suffix>` | `<suffix>` |\n| `.env.example` | `example` (documentation only) |\n\nAliases: `dev` → `development`, `prod` → `production` for the `diff` command.\n\n## Output formats\n\n### Human (default)\nColorized, sectioned report as shown above.\n\n### JSON (`--json` / `--format json`)\n```json\n{\n  \"exitCode\": 1,\n  \"summary\": {\n    \"filesScanned\": 8,\n    \"variablesFound\": 15,\n    \"errors\": 4,\n    \"warnings\": 16,\n    \"infos\": 0,\n    \"total\": 20\n  },\n  \"findings\": [\n    {\n      \"id\": \"missing.COMPOSE_ONLY\",\n      \"ruleId\": \"missing\",\n      \"severity\": \"error\",\n      \"variable\": \"COMPOSE_ONLY\",\n      \"message\": \"referenced but not defined in any environment file\",\n      \"locations\": [\n        { \"file\": \"docker-compose.yml\", \"line\": 9, \"kind\": \"definition\" }\n      ]\n    }\n  ]\n}\n```\n\n### SARIF (`--format sarif`)\nEmits [SARIF 2.1.0](https://sarifweb.azurewebsites.net/) for upload to GitHub\ncode scanning or any SARIF-aware tool.\n\n## CI integration\n\n```yaml\n# .github/workflows/env-audit.yml\nname: Environment Audit\non: [push, pull_request]\njobs:\n  envdoctor:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v5\n      - uses: actions/setup-node@v5\n        with:\n          node-version: '22'\n      - run: npx @arunskg/envdoctor scan --strict\n```\n\nFor code scanning, add `--format sarif` and upload the result with\n`github/codeql-action/upload-sarif`.\n\n## Security\n\n- **Values are never printed** to stdout/stderr (even with `--verbose`).\n- **Secrets are never written** to generated files (`.env.example`, `ENVIRONMENT.md`, `.github/ENVIRONMENT.md`, `env.d.ts`).\n- **No network calls, no telemetry** — everything runs locally.\n- Secret heuristic: name matches `/(SECRET|TOKEN|PASSWORD|PASS|API[_A-Z]*KEY|PRIVATE[_-]?KEY|CREDENTIALS)/i`.\n- Unreadable directories are skipped rather than aborting the scan.\n\n## Architecture\n\n```\ndiscovery (fast-glob)\n    │\n    ▼\nparsers (dotenv, docker-compose, kubernetes, github-actions, source)\n    │\n    ▼\nnormalized ProjectModel (definitions + usages per file)\n    │\n    ▼\nindex (buildIndex: maps by name + environment)\n    │\n    ▼\ndetectors (missing, undefined-in-source, unused, duplicates,\n           environment-diff, type-mismatch, schema-validation,\n           public-prefix, weak-secret, typo)\n    │\n    ▼\nAuditResult (Findings + Summary + ExitCode)\n    │\n    ▼\ngenerators (env-example, environment-doc, env-types, schema, github-actions)\n```\n\nEvery parser implements a common `Parser` interface and every detector a common\n`Detector` interface — new formats and rules can be added without touching the\nothers.\n\n## Development\n\n```bash\nnpm install       # install deps\nnpm test          # run the test suite (vitest)\nnpm run typecheck # tsc --noEmit\nnpm run lint      # eslint\nnpm run build     # tsup → dist/\n\n# Local smoke test\nnode dist/index.js scan --dir tests/fixtures/sample-project\n```\n\n## Contributing\n\nIssues and pull requests are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md)\nfor the development workflow — please run `npm test`, `npm run lint`, and\n`npm run typecheck` before opening a PR. See [CHANGELOG.md](./CHANGELOG.md) for\nrelease history and [SECURITY.md](./SECURITY.md) to report a vulnerability.\n\nLooking for something to work on? The [ROADMAP.md](./ROADMAP.md) tracks planned\nwork, and issues labelled [`help wanted`](https://github.com/arun-skg/envdoctor/labels/help%20wanted)\nand [`good first issue`](https://github.com/arun-skg/envdoctor/labels/good%20first%20issue)\nare great starting points — including native ports to new languages.\n\n## Download trends\n\n<details>\n<summary>Consolidated downloads across ecosystems, last 90 days (auto-refreshed daily)</summary>\n\n<img src=\"https://raw.githubusercontent.com/arun-skg/envdoctor/npm-downloads/downloads.svg\" alt=\"Consolidated envdoctor downloads across ecosystems, last 90 days\" width=\"100%\">\n\n<sub>Consolidated across ecosystems, auto-refreshed daily by the <a href=\"./.github/workflows/downloads-chart.yml\">Downloads chart</a> workflow. Daily trend lines are shown for npm and PyPI (the registries that publish a time-series); RubyGems and Packagist show current totals. Maven Central and Go do not publish download statistics.</sub>\n\n</details>\n\n## Support\n\nenvdoctor is free and MIT-licensed. If it's saved you from a broken deploy,\nyou can support ongoing development:\n\n- ❤️ [GitHub Sponsors](https://github.com/sponsors/arun-skg)\n- ☕ [Buy Me a Coffee](https://buymeacoffee.com/arunskg)\n\nStarring the repo and telling a teammate helps just as much.\n\n## License\n\n[MIT](./LICENSE)\n","readmeFilename":"README.md"}