{"_id":"@adamwade2384/envcheck","name":"@adamwade2384/envcheck","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@adamwade2384/envcheck","version":"0.1.0","description":"Lint, diff, sync, and mask .env files. Keep .env and .env.example honest.","keywords":["env","dotenv","environment","lint","diff","sync","cli","ci"],"license":"MIT","author":{"name":"Adam Wade","email":"adam@inketix.com"},"repository":{"type":"git","url":"git+https://github.com/adamcwade/envcheck.git"},"bugs":{"url":"https://github.com/adamcwade/envcheck/issues"},"homepage":"https://github.com/adamcwade/envcheck#readme","type":"module","bin":{"envcheck":"dist/index.js"},"main":"dist/index.js","engines":{"node":">=20"},"scripts":{"build":"tsc -p tsconfig.build.json","typecheck":"tsc -p tsconfig.json --noEmit","test":"vitest run","prepublishOnly":"npm run typecheck && npm run build"},"dependencies":{"commander":"^12.1.0","picocolors":"^1.1.1"},"devDependencies":{"@types/node":"^20.17.0","typescript":"^5.6.0","vitest":"^2.1.0"},"_id":"@adamwade2384/envcheck@0.1.0","gitHead":"de23eddcc77bc00ab3afa146dab613394c109cc8","types":"./dist/index.d.ts","_nodeVersion":"21.7.3","_npmVersion":"10.5.0","dist":{"integrity":"sha512-OuqS37uUfHaWzEKH70hYtof4IhZU7oKQ6WCvLHIHNTISnLES6GMffuwqjvZcapAXw8kY2Pa59EdWpL7vApo3+g==","shasum":"b532814db6190109180bfc3263953a5e875e9139","tarball":"https://registry.npmjs.org/@adamwade2384/envcheck/-/envcheck-0.1.0.tgz","fileCount":15,"unpackedSize":41667,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIFkcFrnuj9DvaJs/U1iwfyo3k1CWdEgwKxpm+EQ2X4D5AiBYa43T0hSDHVKhWd6ReGVfbtGj+LDmoh4Oq7zfm/2dNQ=="}]},"_npmUser":{"name":"adamwade2384","email":"adamwade2384@gmail.com"},"directories":{},"maintainers":[{"name":"adamwade2384","email":"adamwade2384@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/envcheck_0.1.0_1781186331270_0.46084679990012045"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-11T13:58:51.150Z","0.1.0":"2026-06-11T13:58:51.409Z","modified":"2026-06-11T13:58:51.604Z"},"maintainers":[{"name":"adamwade2384","email":"adamwade2384@gmail.com"}],"description":"Lint, diff, sync, and mask .env files. Keep .env and .env.example honest.","homepage":"https://github.com/adamcwade/envcheck#readme","keywords":["env","dotenv","environment","lint","diff","sync","cli","ci"],"repository":{"type":"git","url":"git+https://github.com/adamcwade/envcheck.git"},"author":{"name":"Adam Wade","email":"adam@inketix.com"},"bugs":{"url":"https://github.com/adamcwade/envcheck/issues"},"license":"MIT","readme":"# envcheck\n\nLint, diff, sync, and mask `.env` files. Keep `.env` and `.env.example` honest.\n\n## The pain\n\nYou pull main, the app crashes with `undefined is not a string`, and twenty\nminutes later you find out a teammate added `SENDGRID_API_KEY` last week.\nIt is in `.env.example`. It never made it to your `.env`. Nobody told you,\nbecause nobody knew they had to.\n\nThe reverse happens too: your `.env` grows keys that never get documented in\n`.env.example`, the file collects duplicates and dead lines, and one day\nsomeone pastes the whole thing into Slack to debug something, secrets and all.\n\n`envcheck` is a small CLI that makes env files boring again:\n\n* **lint** catches duplicates, malformed lines, and footguns before they bite\n* **diff** shows exactly how `.env` and `.env.example` drifted apart\n* **sync** copies missing keys (with their comments) into your `.env`\n* **check** is a one-word command for git hooks and CI\n* **mask** prints an env file with secrets redacted so you can share it\n\nNo runtime config, no daemon, two tiny dependencies, a hand-written parser\nthat understands real-world env files (quotes, escapes, `export`, CRLF,\nmultiline values, inline comments).\n\n## Install\n\n```sh\nnpm install -g envcheck-cli   # installs the `envcheck` command\n# or run it without installing:\nnpx envcheck-cli check\n```\n\nRequires Node 20 or newer.\n\n## Quick start\n\n```sh\nenvcheck check            # diff .env against .env.example in the cwd\nenvcheck lint .env        # find problems in a single file\nenvcheck sync .env --from .env.example   # add the keys you are missing\n```\n\n## Commands\n\nEvery command supports `--json` for machine-readable output. Colors are used\nonly when stdout is a TTY and `NO_COLOR` is unset.\n\n### `envcheck lint <file>`\n\nParses the file and reports problems. Errors (duplicate keys, malformed\nlines) exit with code 1; warnings alone exit 0.\n\n```text\n$ envcheck lint fixtures/messy.env\nfixtures/messy.env\n  line 3    error   \"API_KEY\" is defined more than once (first defined on line 2)  duplicate-key\n  line 4    warning \"DB_HOST\" has an empty value  empty-value\n  line 5    warning \"GREETING\" has an unquoted value containing spaces (quote it to avoid surprises)  unquoted-space\n  line 7    warning \"apiToken\" is not UPPER_SNAKE_CASE  key-case\n  line 8    error   missing '=' separator  malformed-line\n\n2 errors, 3 warnings\n```\n\nRules:\n\n| Rule | Severity | What it catches |\n| --- | --- | --- |\n| `duplicate-key` | error | the same key defined twice (the later one silently wins in most loaders) |\n| `malformed-line` | error | no `=`, invalid key, unterminated quote, junk after a closing quote |\n| `empty-value` | warning | `KEY=` with nothing after it |\n| `unquoted-space` | warning | unquoted values containing spaces |\n| `trailing-whitespace` | warning | invisible trailing spaces or tabs |\n| `key-case` | warning | keys that are not `UPPER_SNAKE_CASE` |\n\n### `envcheck diff <a> <b>`\n\nCompares two env files by key. Values are masked by default; pass\n`--show-values` to see them. Built for `.env` vs `.env.example`.\n\n```text\n$ envcheck diff fixtures/.env fixtures/.env.example\nComparing fixtures/.env (A) vs fixtures/.env.example (B)\n\nMissing in fixtures/.env:\n  - SENDGRID_API_KEY\n  - LOG_LEVEL\n\nMissing in fixtures/.env.example:\n  + DEBUG\n\nDifferent values:\n  ~ PORT  A=******  B=******\n  ~ DATABASE_URL  A=po******ev  B=po******pp\n  ~ STRIPE_SECRET_KEY  A=sk******KE  B=sk******me\n\nOut of sync: 2 missing in A, 1 missing in B, 3 different (2 keys match)\n```\n\nExits 0 when the files are in sync, 1 when they are not.\n\n### `envcheck sync <file> --from <example>`\n\nAppends keys that exist in the example but are missing from the target.\nThe target file is never rewritten: existing lines, comments, and key order\nare preserved byte for byte. Missing keys are appended in example order,\ncarrying over the comments that sit directly above them in the example.\nUse `--dry-run` to preview.\n\n```text\n$ envcheck sync .env --from .env.example --dry-run\nWould add 2 keys to .env:\n  + SENDGRID_API_KEY=changeme\n  + # How loud to log. One of: debug, info, warn, error.\n  + LOG_LEVEL=info\n(dry run, nothing written)\n```\n\nDrop `--dry-run` to write. Running it again is a no-op:\n\n```text\n$ envcheck sync .env --from .env.example\n.env already has every key from .env.example\n```\n\n### `envcheck check`\n\nZero-argument mode for hooks and CI. Finds `.env` and `.env.example`\n(falling back to `.env.sample` or `.env.template`) in the current directory\nand diffs them. Same output and exit codes as `diff`; exits 2 if either\nfile cannot be found.\n\n```text\n$ envcheck check\nComparing .env (A) vs .env.example (B)\n\nMissing in .env:\n  - SENDGRID_API_KEY\n  - LOG_LEVEL\n...\nOut of sync: 2 missing in A, 1 missing in B, 3 different (2 keys match)\n```\n\n### `envcheck mask <file>`\n\nPrints the file with secret-looking values redacted, safe to paste into an\nissue or a chat. A value is masked when its key name contains `SECRET`,\n`TOKEN`, `KEY`, `PASSWORD`, `PASSWD`, `PRIVATE`, or `CREDENTIAL`, or when\nthe value itself is a long high-entropy blob (Shannon entropy of at least\n3.7 bits per character, length 16+, no whitespace).\n\n```text\n$ envcheck mask .env\n# App\nNODE_ENV=development\nPORT=4000\n\n# Database\nDATABASE_URL=po******ev\nDB_POOL_SIZE=10\n\n# Third-party APIs\nSTRIPE_SECRET_KEY=sk******KE\nDEBUG=true\n```\n\nNote that `DATABASE_URL` was caught by the entropy heuristic even though\nits key name looks innocent: connection strings embed passwords.\n\n## Exit codes\n\n| Code | Meaning |\n| --- | --- |\n| 0 | success; nothing to report (warnings do not fail `lint`) |\n| 1 | problems found: lint errors, or files out of sync (`diff`, `check`) |\n| 2 | usage error, unreadable file, or `check` could not find the files |\n\n## CI usage\n\n### GitHub Actions\n\n```yaml\njobs:\n  envcheck:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n      # Fail the build if .env.example has lint errors.\n      - run: npx envcheck-cli lint .env.example\n```\n\nCI usually has no `.env` (and should not), so lint the example file there.\nUse `envcheck check` locally, where both files exist.\n\n### husky pre-commit hook\n\n```sh\n# .husky/pre-commit\nnpx envcheck-cli check || {\n  echo \"Your .env is out of sync with .env.example.\"\n  echo \"Run: npx envcheck-cli sync .env --from .env.example\"\n  exit 1\n}\n```\n\n## JSON output\n\nPass `--json` to any command. Shapes:\n\n```jsonc\n// envcheck lint <file> --json\n{\n  \"file\": \"fixtures/messy.env\",\n  \"errors\": 2,\n  \"warnings\": 3,\n  \"issues\": [\n    {\n      \"severity\": \"error\",        // \"error\" | \"warning\"\n      \"rule\": \"duplicate-key\",\n      \"line\": 3,\n      \"key\": \"API_KEY\",           // null for issues without a key\n      \"message\": \"\\\"API_KEY\\\" is defined more than once (first defined on line 2)\"\n    }\n  ]\n}\n```\n\n```jsonc\n// envcheck diff <a> <b> --json   (envcheck check --json is identical)\n{\n  \"a\": \"fixtures/.env\",\n  \"b\": \"fixtures/.env.example\",\n  \"missingInA\": [\"SENDGRID_API_KEY\", \"LOG_LEVEL\"],\n  \"missingInB\": [\"DEBUG\"],\n  \"changed\": [\n    { \"key\": \"PORT\", \"a\": \"******\", \"b\": \"******\" }\n  ],\n  \"equal\": [\"NODE_ENV\", \"DB_POOL_SIZE\"],\n  \"inSync\": false,\n  \"valuesMasked\": true            // false when --show-values is passed\n}\n```\n\n```jsonc\n// envcheck sync <file> --from <example> --json\n{\n  \"file\": \".env\",\n  \"from\": \".env.example\",\n  \"dryRun\": false,\n  \"added\": [\n    { \"key\": \"SENDGRID_API_KEY\", \"value\": \"changeme\" }\n  ]\n}\n```\n\n```jsonc\n// envcheck mask <file> --json\n{\n  \"file\": \".env\",\n  \"maskedCount\": 2,\n  \"entries\": [\n    { \"key\": \"DATABASE_URL\", \"value\": \"po******ev\", \"masked\": true, \"line\": 6 }\n  ]\n}\n```\n\n## What the parser understands\n\nThe `.env` parser is written from scratch (no `dotenv` dependency) and\nhandles the messy reality of env files:\n\n* `export KEY=value` prefixes\n* single quotes (literal) and double quotes (with `\\n`, `\\t`, `\\\"`, `\\\\`\n  and friends expanded)\n* multiline quoted values (PEM certificates, JSON blobs)\n* full-line comments and inline comments (`KEY=value # note`), including\n  the distinction between `value # comment` and `p#ssword`\n* CRLF line endings\n* duplicate keys (reported by `lint`, last one wins for `diff`)\n\n## Development\n\n```sh\nnpm install\nnpm test          # vitest, table-driven tests for parser/lint/diff/sync/mask\nnpm run typecheck\nnpm run build     # tsc to dist/\n```\n\n## License\n\nMIT, see [LICENSE](LICENSE).\n","readmeFilename":"README.md","_rev":"1-0d66da3e56a6652a729b9b91140d8215"}