@kevinpatil/envguard 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/index.js +307 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kevin Patil
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# @kevinpatil/envguard
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://github.com/kevinpatildxd/envguard/actions/workflows/ci.yml)
|
|
5
|
+
[](https://www.npmjs.com/package/@kevinpatil/envguard)
|
|
6
|
+
[](https://www.npmjs.com/package/@kevinpatil/envguard)
|
|
7
|
+
|
|
8
|
+
Validate your `.env` file against `.env.example` before your app ships.
|
|
9
|
+
|
|
10
|
+
Catches missing keys, insecure defaults, type mismatches, weak secrets, and more — in a single fast command.
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
$ npx @kevinpatil/envguard
|
|
14
|
+
|
|
15
|
+
envguard — checking .env against .env.example
|
|
16
|
+
|
|
17
|
+
ERRORS (2)
|
|
18
|
+
✗ DATABASE_URL — Missing required key (defined in .env.example)
|
|
19
|
+
✗ JWT_SECRET — Insecure placeholder value: 'secret'
|
|
20
|
+
|
|
21
|
+
WARNINGS (2)
|
|
22
|
+
⚠ PORT — Expected a number but got 'abc'
|
|
23
|
+
⚠ STRIPE_KEY — Key is not declared in .env.example
|
|
24
|
+
|
|
25
|
+
2 error(s) found. Fix them before deploying.
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install --save-dev @kevinpatil/envguard
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or run without installing:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx @kevinpatil/envguard
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Validate .env against .env.example in the current directory
|
|
48
|
+
npx @kevinpatil/envguard
|
|
49
|
+
|
|
50
|
+
# Target a specific env file
|
|
51
|
+
npx @kevinpatil/envguard --env .env.production
|
|
52
|
+
|
|
53
|
+
# Use a custom example file
|
|
54
|
+
npx @kevinpatil/envguard --example .env.example.production
|
|
55
|
+
|
|
56
|
+
# Exit with code 1 if any errors are found (for CI)
|
|
57
|
+
npx @kevinpatil/envguard --strict
|
|
58
|
+
|
|
59
|
+
# Output results as JSON
|
|
60
|
+
npx @kevinpatil/envguard --json
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Validation Rules
|
|
66
|
+
|
|
67
|
+
| Rule | Severity | Description |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `missing-key` | ERROR | Key defined in `.env.example` is absent from `.env` |
|
|
70
|
+
| `empty-value` | ERROR | Key is present but has no value |
|
|
71
|
+
| `insecure-defaults` | ERROR | Value matches a known insecure placeholder (`changeme`, `secret`, `todo`…) |
|
|
72
|
+
| `undeclared-key` | WARNING | Key exists in `.env` but is not in `.env.example` |
|
|
73
|
+
| `weak-secret` | WARNING | Secret key is under 16 characters |
|
|
74
|
+
| `type-mismatch` | WARNING | Numeric key (`PORT`, `TIMEOUT`…) has a non-numeric value |
|
|
75
|
+
| `malformed-url` | WARNING | URL key has a value with a missing or unrecognized protocol |
|
|
76
|
+
| `boolean-mismatch` | WARNING | Boolean key (`FEATURE_*`, `ENABLE_*`…) has a non-boolean value |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## CI Integration
|
|
81
|
+
|
|
82
|
+
Add envguard to your pipeline to block deployments with bad config:
|
|
83
|
+
|
|
84
|
+
### GitHub Actions
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
- name: Validate environment variables
|
|
88
|
+
run: npx @kevinpatil/envguard --strict
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Any CI
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npx @kevinpatil/envguard --strict # exits with code 1 if errors are found
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### JSON output for custom pipelines
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npx @kevinpatil/envguard --json | jq '.[] | select(.severity == "error")'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## How it works
|
|
106
|
+
|
|
107
|
+
1. Reads `.env.example` as the source of truth
|
|
108
|
+
2. Reads your `.env` file
|
|
109
|
+
3. Runs all validation rules against both
|
|
110
|
+
4. Prints a color-coded report to the terminal
|
|
111
|
+
5. In `--strict` mode, exits with code `1` if any errors are found
|
|
112
|
+
|
|
113
|
+
No config files required. No API keys. Works offline, in Docker, everywhere.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Contributing
|
|
118
|
+
|
|
119
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT © [Kevin Patil](https://github.com/kevinpatildxd)
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/index.ts
|
|
27
|
+
var import_commander = require("commander");
|
|
28
|
+
var import_path = __toESM(require("path"));
|
|
29
|
+
|
|
30
|
+
// src/parser.ts
|
|
31
|
+
var import_fs = __toESM(require("fs"));
|
|
32
|
+
function parseLines(content) {
|
|
33
|
+
const map = /* @__PURE__ */ new Map();
|
|
34
|
+
for (const rawLine of content.split("\n")) {
|
|
35
|
+
const line = rawLine.trim();
|
|
36
|
+
if (!line || line.startsWith("#")) continue;
|
|
37
|
+
const eqIndex = line.indexOf("=");
|
|
38
|
+
if (eqIndex === -1) continue;
|
|
39
|
+
const key = line.slice(0, eqIndex).trim();
|
|
40
|
+
if (!key) continue;
|
|
41
|
+
let value = line.slice(eqIndex + 1).trim();
|
|
42
|
+
const commentIndex = value.indexOf(" #");
|
|
43
|
+
if (commentIndex !== -1) {
|
|
44
|
+
value = value.slice(0, commentIndex).trim();
|
|
45
|
+
}
|
|
46
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
47
|
+
value = value.slice(1, -1);
|
|
48
|
+
}
|
|
49
|
+
map.set(key, value);
|
|
50
|
+
}
|
|
51
|
+
return map;
|
|
52
|
+
}
|
|
53
|
+
function parseEnvFile(filePath) {
|
|
54
|
+
if (!import_fs.default.existsSync(filePath)) {
|
|
55
|
+
throw new Error(`File not found: ${filePath}`);
|
|
56
|
+
}
|
|
57
|
+
const content = import_fs.default.readFileSync(filePath, "utf-8");
|
|
58
|
+
return parseLines(content);
|
|
59
|
+
}
|
|
60
|
+
function parseEnvExample(filePath) {
|
|
61
|
+
if (!import_fs.default.existsSync(filePath)) {
|
|
62
|
+
throw new Error(`File not found: ${filePath}`);
|
|
63
|
+
}
|
|
64
|
+
const content = import_fs.default.readFileSync(filePath, "utf-8");
|
|
65
|
+
return parseLines(content);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/rules/missing-key.ts
|
|
69
|
+
function missingKey(env, example) {
|
|
70
|
+
const results = [];
|
|
71
|
+
for (const key of example.keys()) {
|
|
72
|
+
if (!env.has(key)) {
|
|
73
|
+
results.push({
|
|
74
|
+
rule: "missing-key",
|
|
75
|
+
severity: "error",
|
|
76
|
+
key,
|
|
77
|
+
message: `Missing required key (defined in .env.example)`
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return results;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/rules/empty-value.ts
|
|
85
|
+
function emptyValue(env, example) {
|
|
86
|
+
const results = [];
|
|
87
|
+
for (const key of example.keys()) {
|
|
88
|
+
if (env.has(key) && env.get(key) === "") {
|
|
89
|
+
results.push({
|
|
90
|
+
rule: "empty-value",
|
|
91
|
+
severity: "error",
|
|
92
|
+
key,
|
|
93
|
+
message: `Value is empty`
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return results;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/rules/undeclared-key.ts
|
|
101
|
+
function undeclaredKey(env, example) {
|
|
102
|
+
const results = [];
|
|
103
|
+
for (const key of env.keys()) {
|
|
104
|
+
if (!example.has(key)) {
|
|
105
|
+
results.push({
|
|
106
|
+
rule: "undeclared-key",
|
|
107
|
+
severity: "warning",
|
|
108
|
+
key,
|
|
109
|
+
message: `Key is not declared in .env.example`
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return results;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/rules/insecure-defaults.ts
|
|
117
|
+
var INSECURE_VALUES = /* @__PURE__ */ new Set([
|
|
118
|
+
"changeme",
|
|
119
|
+
"change_me",
|
|
120
|
+
"todo",
|
|
121
|
+
"secret",
|
|
122
|
+
"password",
|
|
123
|
+
"1234",
|
|
124
|
+
"12345",
|
|
125
|
+
"123456",
|
|
126
|
+
"test",
|
|
127
|
+
"example",
|
|
128
|
+
"placeholder",
|
|
129
|
+
"dummy",
|
|
130
|
+
"fake",
|
|
131
|
+
"temp"
|
|
132
|
+
]);
|
|
133
|
+
function insecureDefaults(env, _example) {
|
|
134
|
+
const results = [];
|
|
135
|
+
for (const [key, value] of env.entries()) {
|
|
136
|
+
if (INSECURE_VALUES.has(value.toLowerCase())) {
|
|
137
|
+
results.push({
|
|
138
|
+
rule: "insecure-defaults",
|
|
139
|
+
severity: "error",
|
|
140
|
+
key,
|
|
141
|
+
message: `Insecure placeholder value: '${value}'`
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return results;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/rules/weak-secret.ts
|
|
149
|
+
var SECRET_PATTERN = /(_SECRET|_KEY|_TOKEN|_PASSWORD|_PASS|_PWD|^JWT_|^API_)/i;
|
|
150
|
+
var MIN_LENGTH = 16;
|
|
151
|
+
function weakSecret(env, _example) {
|
|
152
|
+
const results = [];
|
|
153
|
+
for (const [key, value] of env.entries()) {
|
|
154
|
+
if (SECRET_PATTERN.test(key) && value.length > 0 && value.length < MIN_LENGTH) {
|
|
155
|
+
results.push({
|
|
156
|
+
rule: "weak-secret",
|
|
157
|
+
severity: "warning",
|
|
158
|
+
key,
|
|
159
|
+
message: `Secret is too short (${value.length} chars, minimum is ${MIN_LENGTH})`
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return results;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/rules/type-mismatch.ts
|
|
167
|
+
var NUMERIC_PATTERN = /^(PORT|TIMEOUT|MAX_|MIN_|LIMIT|RETRY_|WORKERS|THREADS)/i;
|
|
168
|
+
function typeMismatch(env, _example) {
|
|
169
|
+
const results = [];
|
|
170
|
+
for (const [key, value] of env.entries()) {
|
|
171
|
+
if (NUMERIC_PATTERN.test(key) && value !== "" && isNaN(Number(value))) {
|
|
172
|
+
results.push({
|
|
173
|
+
rule: "type-mismatch",
|
|
174
|
+
severity: "warning",
|
|
175
|
+
key,
|
|
176
|
+
message: `Expected a number but got '${value}'`
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return results;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/rules/malformed-url.ts
|
|
184
|
+
var URL_PATTERN = /(_URL|_URI|_HOST|^DATABASE_|^REDIS_|^MONGO_)/i;
|
|
185
|
+
var VALID_PROTOCOLS = /* @__PURE__ */ new Set([
|
|
186
|
+
"http:",
|
|
187
|
+
"https:",
|
|
188
|
+
"postgres:",
|
|
189
|
+
"postgresql:",
|
|
190
|
+
"mysql:",
|
|
191
|
+
"mongodb:",
|
|
192
|
+
"mongodb+srv:",
|
|
193
|
+
"redis:",
|
|
194
|
+
"rediss:",
|
|
195
|
+
"amqp:",
|
|
196
|
+
"amqps:",
|
|
197
|
+
"ftp:",
|
|
198
|
+
"ftps:"
|
|
199
|
+
]);
|
|
200
|
+
function isValidUrl(value) {
|
|
201
|
+
try {
|
|
202
|
+
const url = new URL(value);
|
|
203
|
+
return VALID_PROTOCOLS.has(url.protocol);
|
|
204
|
+
} catch {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function malformedUrl(env, _example) {
|
|
209
|
+
const results = [];
|
|
210
|
+
for (const [key, value] of env.entries()) {
|
|
211
|
+
if (URL_PATTERN.test(key) && value !== "" && !isValidUrl(value)) {
|
|
212
|
+
results.push({
|
|
213
|
+
rule: "malformed-url",
|
|
214
|
+
severity: "warning",
|
|
215
|
+
key,
|
|
216
|
+
message: `Value does not appear to be a valid URL: '${value}'`
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return results;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/rules/boolean-mismatch.ts
|
|
224
|
+
var BOOLEAN_PATTERN = /^(FEATURE_|ENABLE_|DISABLE_|IS_|USE_|ALLOW_|FLAG_)/i;
|
|
225
|
+
var VALID_BOOLEANS = /* @__PURE__ */ new Set(["true", "false", "1", "0"]);
|
|
226
|
+
function booleanMismatch(env, _example) {
|
|
227
|
+
const results = [];
|
|
228
|
+
for (const [key, value] of env.entries()) {
|
|
229
|
+
if (BOOLEAN_PATTERN.test(key) && value !== "" && !VALID_BOOLEANS.has(value.toLowerCase())) {
|
|
230
|
+
results.push({
|
|
231
|
+
rule: "boolean-mismatch",
|
|
232
|
+
severity: "warning",
|
|
233
|
+
key,
|
|
234
|
+
message: `Expected a boolean (true/false/1/0) but got '${value}'`
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return results;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/validator.ts
|
|
242
|
+
function validate(env, example) {
|
|
243
|
+
return [
|
|
244
|
+
...missingKey(env, example),
|
|
245
|
+
...emptyValue(env, example),
|
|
246
|
+
...undeclaredKey(env, example),
|
|
247
|
+
...insecureDefaults(env, example),
|
|
248
|
+
...weakSecret(env, example),
|
|
249
|
+
...typeMismatch(env, example),
|
|
250
|
+
...malformedUrl(env, example),
|
|
251
|
+
...booleanMismatch(env, example)
|
|
252
|
+
];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/reporter.ts
|
|
256
|
+
var import_chalk = __toESM(require("chalk"));
|
|
257
|
+
function report(results, options = {}) {
|
|
258
|
+
if (options.json) {
|
|
259
|
+
console.log(JSON.stringify(results, null, 2));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const errors = results.filter((r) => r.severity === "error");
|
|
263
|
+
const warnings = results.filter((r) => r.severity === "warning");
|
|
264
|
+
const passedCount = results.length === 0 ? "all checks" : "remaining keys";
|
|
265
|
+
console.log(import_chalk.default.bold("\nenvguard \u2014 checking .env against .env.example\n"));
|
|
266
|
+
if (errors.length > 0) {
|
|
267
|
+
console.log(import_chalk.default.red.bold(`ERRORS (${errors.length})`));
|
|
268
|
+
for (const r of errors) {
|
|
269
|
+
console.log(import_chalk.default.red(` \u2717 ${r.key} \u2014 ${r.message}`));
|
|
270
|
+
}
|
|
271
|
+
console.log();
|
|
272
|
+
}
|
|
273
|
+
if (warnings.length > 0) {
|
|
274
|
+
console.log(import_chalk.default.yellow.bold(`WARNINGS (${warnings.length})`));
|
|
275
|
+
for (const r of warnings) {
|
|
276
|
+
console.log(import_chalk.default.yellow(` \u26A0 ${r.key} \u2014 ${r.message}`));
|
|
277
|
+
}
|
|
278
|
+
console.log();
|
|
279
|
+
}
|
|
280
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
281
|
+
console.log(import_chalk.default.green.bold(" \u2714 All checks passed"));
|
|
282
|
+
} else {
|
|
283
|
+
console.log(import_chalk.default.green(`PASSED \u2014 ${passedCount} ok`));
|
|
284
|
+
}
|
|
285
|
+
console.log();
|
|
286
|
+
if (errors.length > 0) {
|
|
287
|
+
console.log(import_chalk.default.red.bold(`${errors.length} error(s) found. Fix them before deploying.`));
|
|
288
|
+
} else if (warnings.length > 0) {
|
|
289
|
+
console.log(import_chalk.default.yellow(`${warnings.length} warning(s). Review before deploying.`));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/index.ts
|
|
294
|
+
var program = new import_commander.Command();
|
|
295
|
+
program.name("envguard").description("Validate .env files against .env.example before your app ships").version("1.0.0").option("--env <file>", "path to .env file", ".env").option("--example <file>", "path to .env.example file", ".env.example").option("--strict", "exit with code 1 if any errors are found").option("--json", "output results as JSON").action((options) => {
|
|
296
|
+
const envPath = import_path.default.resolve(process.cwd(), options.env);
|
|
297
|
+
const examplePath = import_path.default.resolve(process.cwd(), options.example);
|
|
298
|
+
const env = parseEnvFile(envPath);
|
|
299
|
+
const example = parseEnvExample(examplePath);
|
|
300
|
+
const results = validate(env, example);
|
|
301
|
+
report(results, { json: options.json });
|
|
302
|
+
const hasErrors = results.some((r) => r.severity === "error");
|
|
303
|
+
if (options.strict && hasErrors) {
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kevinpatil/envguard",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool that validates .env files against .env.example before your app ships",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"envguard": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"dev": "tsup --watch"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"cli",
|
|
20
|
+
"dotenv",
|
|
21
|
+
"env",
|
|
22
|
+
"environment-variables",
|
|
23
|
+
"validation",
|
|
24
|
+
"devtools",
|
|
25
|
+
"typescript"
|
|
26
|
+
],
|
|
27
|
+
"author": "Kevin Patil",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/kevinpatildxd/envguard.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/kevinpatildxd/envguard/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/kevinpatildxd/envguard#readme",
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"chalk": "^4.1.2",
|
|
42
|
+
"commander": "^12.1.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^20.0.0",
|
|
46
|
+
"tsup": "^8.1.0",
|
|
47
|
+
"typescript": "^5.4.5",
|
|
48
|
+
"vitest": "^1.6.0"
|
|
49
|
+
}
|
|
50
|
+
}
|