npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@adamwade2384/envcheck

v0.1.0

Published

Lint, diff, sync, and mask .env files. Keep .env and .env.example honest.

Downloads

59

Readme

envcheck

Lint, diff, sync, and mask .env files. Keep .env and .env.example honest.

The pain

You pull main, the app crashes with undefined is not a string, and twenty minutes later you find out a teammate added SENDGRID_API_KEY last week. It is in .env.example. It never made it to your .env. Nobody told you, because nobody knew they had to.

The reverse happens too: your .env grows keys that never get documented in .env.example, the file collects duplicates and dead lines, and one day someone pastes the whole thing into Slack to debug something, secrets and all.

envcheck is a small CLI that makes env files boring again:

  • lint catches duplicates, malformed lines, and footguns before they bite
  • diff shows exactly how .env and .env.example drifted apart
  • sync copies missing keys (with their comments) into your .env
  • check is a one-word command for git hooks and CI
  • mask prints an env file with secrets redacted so you can share it

No runtime config, no daemon, two tiny dependencies, a hand-written parser that understands real-world env files (quotes, escapes, export, CRLF, multiline values, inline comments).

Install

npm install -g envcheck-cli   # installs the `envcheck` command
# or run it without installing:
npx envcheck-cli check

Requires Node 20 or newer.

Quick start

envcheck check            # diff .env against .env.example in the cwd
envcheck lint .env        # find problems in a single file
envcheck sync .env --from .env.example   # add the keys you are missing

Commands

Every command supports --json for machine-readable output. Colors are used only when stdout is a TTY and NO_COLOR is unset.

envcheck lint <file>

Parses the file and reports problems. Errors (duplicate keys, malformed lines) exit with code 1; warnings alone exit 0.

$ envcheck lint fixtures/messy.env
fixtures/messy.env
  line 3    error   "API_KEY" is defined more than once (first defined on line 2)  duplicate-key
  line 4    warning "DB_HOST" has an empty value  empty-value
  line 5    warning "GREETING" has an unquoted value containing spaces (quote it to avoid surprises)  unquoted-space
  line 7    warning "apiToken" is not UPPER_SNAKE_CASE  key-case
  line 8    error   missing '=' separator  malformed-line

2 errors, 3 warnings

Rules:

| Rule | Severity | What it catches | | --- | --- | --- | | duplicate-key | error | the same key defined twice (the later one silently wins in most loaders) | | malformed-line | error | no =, invalid key, unterminated quote, junk after a closing quote | | empty-value | warning | KEY= with nothing after it | | unquoted-space | warning | unquoted values containing spaces | | trailing-whitespace | warning | invisible trailing spaces or tabs | | key-case | warning | keys that are not UPPER_SNAKE_CASE |

envcheck diff <a> <b>

Compares two env files by key. Values are masked by default; pass --show-values to see them. Built for .env vs .env.example.

$ envcheck diff fixtures/.env fixtures/.env.example
Comparing fixtures/.env (A) vs fixtures/.env.example (B)

Missing in fixtures/.env:
  - SENDGRID_API_KEY
  - LOG_LEVEL

Missing in fixtures/.env.example:
  + DEBUG

Different values:
  ~ PORT  A=******  B=******
  ~ DATABASE_URL  A=po******ev  B=po******pp
  ~ STRIPE_SECRET_KEY  A=sk******KE  B=sk******me

Out of sync: 2 missing in A, 1 missing in B, 3 different (2 keys match)

Exits 0 when the files are in sync, 1 when they are not.

envcheck sync <file> --from <example>

Appends keys that exist in the example but are missing from the target. The target file is never rewritten: existing lines, comments, and key order are preserved byte for byte. Missing keys are appended in example order, carrying over the comments that sit directly above them in the example. Use --dry-run to preview.

$ envcheck sync .env --from .env.example --dry-run
Would add 2 keys to .env:
  + SENDGRID_API_KEY=changeme
  + # How loud to log. One of: debug, info, warn, error.
  + LOG_LEVEL=info
(dry run, nothing written)

Drop --dry-run to write. Running it again is a no-op:

$ envcheck sync .env --from .env.example
.env already has every key from .env.example

envcheck check

Zero-argument mode for hooks and CI. Finds .env and .env.example (falling back to .env.sample or .env.template) in the current directory and diffs them. Same output and exit codes as diff; exits 2 if either file cannot be found.

$ envcheck check
Comparing .env (A) vs .env.example (B)

Missing in .env:
  - SENDGRID_API_KEY
  - LOG_LEVEL
...
Out of sync: 2 missing in A, 1 missing in B, 3 different (2 keys match)

envcheck mask <file>

Prints the file with secret-looking values redacted, safe to paste into an issue or a chat. A value is masked when its key name contains SECRET, TOKEN, KEY, PASSWORD, PASSWD, PRIVATE, or CREDENTIAL, or when the value itself is a long high-entropy blob (Shannon entropy of at least 3.7 bits per character, length 16+, no whitespace).

$ envcheck mask .env
# App
NODE_ENV=development
PORT=4000

# Database
DATABASE_URL=po******ev
DB_POOL_SIZE=10

# Third-party APIs
STRIPE_SECRET_KEY=sk******KE
DEBUG=true

Note that DATABASE_URL was caught by the entropy heuristic even though its key name looks innocent: connection strings embed passwords.

Exit codes

| Code | Meaning | | --- | --- | | 0 | success; nothing to report (warnings do not fail lint) | | 1 | problems found: lint errors, or files out of sync (diff, check) | | 2 | usage error, unreadable file, or check could not find the files |

CI usage

GitHub Actions

jobs:
  envcheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      # Fail the build if .env.example has lint errors.
      - run: npx envcheck-cli lint .env.example

CI usually has no .env (and should not), so lint the example file there. Use envcheck check locally, where both files exist.

husky pre-commit hook

# .husky/pre-commit
npx envcheck-cli check || {
  echo "Your .env is out of sync with .env.example."
  echo "Run: npx envcheck-cli sync .env --from .env.example"
  exit 1
}

JSON output

Pass --json to any command. Shapes:

// envcheck lint <file> --json
{
  "file": "fixtures/messy.env",
  "errors": 2,
  "warnings": 3,
  "issues": [
    {
      "severity": "error",        // "error" | "warning"
      "rule": "duplicate-key",
      "line": 3,
      "key": "API_KEY",           // null for issues without a key
      "message": "\"API_KEY\" is defined more than once (first defined on line 2)"
    }
  ]
}
// envcheck diff <a> <b> --json   (envcheck check --json is identical)
{
  "a": "fixtures/.env",
  "b": "fixtures/.env.example",
  "missingInA": ["SENDGRID_API_KEY", "LOG_LEVEL"],
  "missingInB": ["DEBUG"],
  "changed": [
    { "key": "PORT", "a": "******", "b": "******" }
  ],
  "equal": ["NODE_ENV", "DB_POOL_SIZE"],
  "inSync": false,
  "valuesMasked": true            // false when --show-values is passed
}
// envcheck sync <file> --from <example> --json
{
  "file": ".env",
  "from": ".env.example",
  "dryRun": false,
  "added": [
    { "key": "SENDGRID_API_KEY", "value": "changeme" }
  ]
}
// envcheck mask <file> --json
{
  "file": ".env",
  "maskedCount": 2,
  "entries": [
    { "key": "DATABASE_URL", "value": "po******ev", "masked": true, "line": 6 }
  ]
}

What the parser understands

The .env parser is written from scratch (no dotenv dependency) and handles the messy reality of env files:

  • export KEY=value prefixes
  • single quotes (literal) and double quotes (with \n, \t, \", \\ and friends expanded)
  • multiline quoted values (PEM certificates, JSON blobs)
  • full-line comments and inline comments (KEY=value # note), including the distinction between value # comment and p#ssword
  • CRLF line endings
  • duplicate keys (reported by lint, last one wins for diff)

Development

npm install
npm test          # vitest, table-driven tests for parser/lint/diff/sync/mask
npm run typecheck
npm run build     # tsc to dist/

License

MIT, see LICENSE.