envpin
v0.1.0
Published
env config with a schema, a boot time report and a lockfile so drift is a diff
Maintainers
Readme
envpin
env config with a schema, a boot time report and a lockfile so drift is a diff.
zero deps. node 18+.
npm i envpinwhy
most env handling fails one key at a time. you fix DATABASE_URL, restart, and
now STRIPE_KEY is missing. five restarts later you are up. envpin reports
every problem in one go, then hands you typed values.
it also writes a lock of the schema shape — never the values — so a teammate adding a required key shows up as a diff in code review instead of a page at 3am.
use
import { loadOrThrow } from 'envpin'
const env = loadOrThrow({
PORT: { kind: 'port', default: 3000 },
DATABASE_URL: { kind: 'url', describe: 'primary postgres' },
LOG_LEVEL: { kind: 'enum', values: ['debug', 'info', 'warn'], default: 'info' },
STRIPE_KEY: { secret: true },
WORKERS: { kind: 'number', min: 1, max: 64, default: 4 }
})
env.PORT // 3000, a number
env.DEBUG // false, a booleana bad boot prints everything at once and exits:
env config failed: 2 errors
✗ DATABASE_URL is required (url) - primary postgres
✗ STRIPE_KEY is required
! APP_TYOP set but not in the schemakinds
| kind | accepts | gives back |
|---|---|---|
| string | anything | string |
| number | numeric, honours min / max | number |
| port | integer 1..65535 | number |
| boolean | 1 true yes on / 0 false no off | boolean |
| url | anything new URL() takes | string |
| enum | one of values | string |
| json | valid json | parsed |
default also covers the empty string, because PORT= in a .env is unset in
every way that matters.
anything with no default is required unless you say required: false.
secrets
secret: true does two things.
it keeps the value out of summarize():
import { load, summarize } from 'envpin'
const { values } = load(schema)
console.log(summarize(schema, values))
// PORT 3000
// STRIPE_KEY [redacted]and it warns when a live-looking key turns up outside production:
! STRIPE_KEY looks like a production credential but NODE_ENV is developmentthat one is a warning, not an error. it does not block boot — it is there for
the moment someone copies prod .env to their laptop. matches sk_live_,
rk_live_, AKIA, ghp_, npm_.
the lock
import { lockOf, diff } from 'envpin'
import previous from './env.lock.json' with { type: 'json' }
const d = diff(previous, lockOf(schema))
if (!d.ok) {
console.log('added', d.added) // ['NEW_KEY']
console.log('removed', d.removed)
console.log('changed', d.changed) // [{ key: 'PORT', from: 'port', to: 'number' }]
process.exit(1)
}the lock is a sha256 over key name, kind, required, secret, has-default —
sorted, so key order in your file never moves the hash. no value ever goes
in. commit env.lock.json.
cli
npx envpin check ./env.schema.js # diff schema against env.lock.json
npx envpin write ./env.schema.js # write the lockthe schema module default-exports the schema object. check exits 1 on drift,
so it drops into ci as one line.
api
load(schema, env?, opts?)→{ values, problems, lock }, never throwsloadOrThrow(schema, env?, opts?)→values, throwsEnvErrorwith the whole reportlockOf(schema)→Lockdiff(previous, current)→{ ok, added, removed, changed }summarize(schema, values)→ printable string, secrets masked
opts.nodeEnv overrides NODE_ENV for the live-key check.
license
MIT
