skip to content
← blog

i published eight npm packages nobody asked for

5 min read

the bug that started this was not interesting. a deploy went out, the container came up, the container fell over. one environment variable was missing in production and present everywhere else.

that is the whole class of failure. not a typo in your code. a typo in a dashboard, or a variable somebody added locally in march and never added to staging, or a value that is present but empty, or present but a string where the code expects a number.

your application does not find out until runtime. usually the runtime after the deploy, in front of users.

envguard is my answer to that. it is published under the @stacklance scope on npm and it is now eight or so packages.

why eight and not one

this is the part i want to explain, because “i split it into eight packages” reads like someone enjoying themselves too much.

the reason is that the same problem shows up at four different moments, and each moment wants a different tool.

while you are writing code. you want your editor to tell you that process.env.STRIPE_KEY is not declared anywhere in your schema, in the editor, in red, before you have finished the line. that is a vs code extension. it cannot be a library, because a library only runs when you run it.

before you commit. you want a command that diffs your .env against your schema and against .env.example, and tells you what drifted. that is a cli.

in ci. you want the pipeline to refuse to build if the environment declared for that environment is incomplete. that is a github action.

at boot. you want the app to validate and fail loudly with a readable message, not a stack trace from three libraries deep. that is the runtime package.

one package cannot be all four. a vs code extension pulled into your production bundle is absurd. a cli shipped as a runtime dependency is bloat. so: a core schema package that defines the contract, and thin packages around it for each moment where you need the contract enforced.

that split is the actual design decision. everything else is packaging.

what i got wrong first

the first version tried to infer your schema by scanning your code for process.env references.

it felt clever. it was wrong roughly a quarter of the time, because environment variables get accessed through wrappers, destructured, aliased into config objects, read in dockerfiles, read in shell scripts. static inference of a runtime lookup is a losing game.

so i deleted it. you declare the schema explicitly now. it is more typing and it is correct, and correct is the entire pitch. a validation tool that is right most of the time is worse than no validation tool, because you stop reading its output.

on open source expectations

i want to be blunt about this part because the indie hacker version of this story usually is not.

publishing eight packages does not produce eight packages worth of attention. it produces a handful of stars, a couple of issues, and the quiet satisfaction of using your own thing every day.

that is fine. i did not build envguard as a growth channel. i built it because i hit the problem on client infrastructure repeatedly, i was solving it ad hoc every time with a different half-written script, and at some point the ad hoc solutions cost more than the real one.

the honest reason to open source something is that you were going to build it anyway and publishing costs you an afternoon.

if you build one for distribution, you will resent it in a month when the readme has more traffic than the package.

what i use it for now

every project i start gets the runtime package and a schema file in the first hour. client work gets the github action, because on a team the drift problem is not a person problem, it is an inevitability problem.

the code is at github.com/fixedbydev. it is not a product, it does not have a pricing page, and it will never have one.