Every Node.js project I've worked on has the same failure mode: a new developer clones the repo, runs npm install, tries to start the server, and gets a cryptic error because some environment variable is missing. .env.example is out of date. Again. Here's the tool I'm building to fix that.
The specific pain point
You add DATABASE_URL to your code on Tuesday. You forget to add it to .env.example. Three weeks later, someone's production deploy fails because they copied .env.example and missed the new variable.
The fix is always: "oh, add that to .env.example." Then you spend twenty minutes figuring out which variables are actually needed, checking the code, checking the deployment docs, hoping nothing was added after the last time someone updated the example file.
The real problem: nothing tells you .env.example is missing a variable until something breaks. The stale .env.example is a silent bug. It doesn't fail when you commit it. It fails three weeks later in someone else's environment.
Why existing tools don't solve it
There are good tools in this space. dotenv-safe has 152,609 downloads last week. envalid has 478,131. They work — but they're all declaration-first: you maintain a list of required variables in a schema file, and the tool validates your environment against that list.
The problem: maintaining the schema is the same work as maintaining .env.example. You still have to remember to update it every time you add a new process.env reference in your code. The schema can go stale for exactly the same reason .env.example goes stale — there's no enforcement, just discipline.
dotenv (91 million weekly downloads) solves loading. These tools solve validation against a declared schema. None of them solve the discovery problem: figuring out which vars your code actually needs.
The insight: the source code already knows
Every time you write process.env.DATABASE_URL in your code, you've implicitly declared that you need DATABASE_URL. That declaration is already there — it's just not extracted anywhere.
I'm building envscan to do that extraction. It scans your .js and .ts files and pulls out every process.env reference:
$ envscan scan
Found 8 environment variables:
DATABASE_URL type: url src/db.ts:12
PORT type: number src/server.ts:5
JWT_SECRET type: secret src/auth.ts:8, src/auth.ts:23
REDIS_URL type: url src/cache.ts:3
SENDGRID_API_KEY type: secret src/email.ts:7
APP_ENV type: string src/config.ts:2
LOG_LEVEL type: string src/config.ts:3
DEBUG type: boolean src/config.ts:4
Validation: 7/8 vars set. Missing: SENDGRID_API_KEY
No config file needed. No schema to maintain. The schema is the codebase.
The type inference is heuristic — it looks at variable names (_URL → url, _SECRET, _KEY, _TOKEN → secret, PORT → number, DEBUG → boolean) — but it's right often enough to be useful as a starting point.
What it generates
envscan generate writes a .env.example with type hints and source locations as comments:
# type: url | Found in: src/db.ts:12
DATABASE_URL=
# type: number | Found in: src/server.ts:5
PORT=
# type: secret | Found in: src/auth.ts:8
JWT_SECRET=
# type: url | Found in: src/cache.ts:3
REDIS_URL=
# type: secret | Found in: src/email.ts:7
SENDGRID_API_KEY=
# type: string | Found in: src/config.ts:2
APP_ENV=
# type: string | Found in: src/config.ts:3
LOG_LEVEL=
# type: boolean | Found in: src/config.ts:4
DEBUG=
Run it in a pre-commit hook. .env.example stays in sync automatically — not because someone remembered to update it, but because it's regenerated from the code.
The validate command compares your current environment against what the scan finds and reports what's missing. You can wire it into your startup script so the process fails loudly and immediately rather than failing cryptically three console.log calls later.
The CI angle (honest about what's not built yet)
The CLI is almost done. What I'm building next is a GitHub Action that runs envscan validate on every pull request and posts an inline comment when new process.env references appear in the diff without a matching .env.example entry — catching the stale .env.example problem before it merges.
That CI tier is the paid part ($6/month). Running GitHub Actions costs money, and I'd like to maintain this long-term rather than abandoning it when hosting costs add up. The CLI will stay free.
I haven't released either yet. The core scanning and generation works. The edge cases I'm still handling: template literals (process.env[key]), computed property access, and variables referenced only in test files. Computed access is genuinely hard — you can't statically know the variable name if it's dynamic. My current approach flags those as a warning rather than silently skipping them.
If this sounds useful
envscan isn't released yet — I'm finishing the CLI now. If this sounds like a problem you've run into, the waitlist is at ckmtools.dev/envscan/ — no credit card, just an email to get notified when it ships.
Feedback welcome on the output format. The file location comments in .env.example — useful or noisy? Let me know in the comments.
Top comments (0)