pretty-env 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 +497 -0
- package/cli/pretty-env-cli.js +154 -0
- package/package.json +60 -0
- package/src/index.js +331 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Your Name
|
|
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,497 @@
|
|
|
1
|
+
# 🔥 pretty-env
|
|
2
|
+
|
|
3
|
+
**Validates and prettifies .env files with zero config**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/pretty-env)
|
|
6
|
+
[](https://nodejs.org/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[]()
|
|
9
|
+
|
|
10
|
+
Every JavaScript project uses `.env` files. `dotenv` loads them, but it doesn't validate anything. **pretty-env** solves this with:
|
|
11
|
+
|
|
12
|
+
✅ **Validation** — Catches typos, missing required keys, security issues
|
|
13
|
+
🔒 **Security warnings** — Detects weak credentials, sensitive unencrypted values
|
|
14
|
+
💚 **Zero config** — Works with one line: `require('pretty-env').load()`
|
|
15
|
+
🎨 **Beautiful output** — Clear error messages with line numbers
|
|
16
|
+
🛠️ **CLI tool** — Validate `.env` files from terminal
|
|
17
|
+
⚡ **Drop-in replacement** — Compatible with `dotenv`
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install pretty-env
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Usage (One Line!)
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
const env = require('pretty-env').load();
|
|
33
|
+
|
|
34
|
+
console.log(env.parsed.DATABASE_URL); // ✅ Validated and loaded
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### In your `.env` file
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Database
|
|
41
|
+
DATABASE_URL=postgresql://user:pass@localhost/mydb
|
|
42
|
+
|
|
43
|
+
# Authentication
|
|
44
|
+
JWT_SECRET=your-secret-key-min-32-chars-is-better-for-security
|
|
45
|
+
API_KEY=sk_live_1234567890abcdef
|
|
46
|
+
|
|
47
|
+
# Settings
|
|
48
|
+
NODE_ENV=production
|
|
49
|
+
PORT=3000
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
### 1. **Automatic Validation**
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
const { load } = require('pretty-env');
|
|
60
|
+
|
|
61
|
+
// Validates format, required keys, security issues
|
|
62
|
+
const env = load({
|
|
63
|
+
required: ['DATABASE_URL', 'JWT_SECRET']
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// ✅ Throws helpful error if keys missing:
|
|
67
|
+
// ❌ Failed to parse .env file (1 error)
|
|
68
|
+
// Validation errors:
|
|
69
|
+
// 1. Required key "DATABASE_URL" is missing
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 2. **Security Warnings**
|
|
73
|
+
|
|
74
|
+
Detects common security issues:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# .env
|
|
78
|
+
DATABASE_PASSWORD=test # ⚠️ Weak value
|
|
79
|
+
API_KEY=abc # ⚠️ Too short
|
|
80
|
+
JWT_SECRET=password123 # 🔴 CRITICAL: Common weak password
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
const env = load({ warnings: true });
|
|
85
|
+
|
|
86
|
+
// Console output:
|
|
87
|
+
// ⚠️ Warnings (3)
|
|
88
|
+
// 1. 🔒 Security risk: "DATABASE_PASSWORD" appears sensitive but value is weak
|
|
89
|
+
// 2. 🔒 Security risk: "API_KEY" appears sensitive but value is weak
|
|
90
|
+
// 3. ⚠️ Insecure value detected in "JWT_SECRET"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3. **Strict Mode** (Whitelist Keys)
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
const env = load({
|
|
97
|
+
strict: true,
|
|
98
|
+
allowed: ['DATABASE_URL', 'PORT', 'NODE_ENV']
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// ❌ Throws if .env has any other keys
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 4. **CLI Tool**
|
|
105
|
+
|
|
106
|
+
Validate from terminal:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Validate current .env
|
|
110
|
+
$ pretty-env validate
|
|
111
|
+
|
|
112
|
+
# Validate specific file
|
|
113
|
+
$ pretty-env validate .env.production
|
|
114
|
+
|
|
115
|
+
# Compare two files
|
|
116
|
+
$ pretty-env compare .env.example .env
|
|
117
|
+
|
|
118
|
+
# Show help
|
|
119
|
+
$ pretty-env help
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Output example:**
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
🔍 Validating .env...
|
|
126
|
+
────────────────────────
|
|
127
|
+
|
|
128
|
+
✅ Valid! Loaded 5 variables
|
|
129
|
+
|
|
130
|
+
📋 Variables:
|
|
131
|
+
DATABASE_URL = postgresql://localhost:5432/mydb
|
|
132
|
+
NODE_ENV = development
|
|
133
|
+
PORT = 3000
|
|
134
|
+
JWT_SECRET = (hidden for security)
|
|
135
|
+
DEBUG = true
|
|
136
|
+
|
|
137
|
+
⚠️ Warnings (2)
|
|
138
|
+
1. Empty value for key "LOG_LEVEL" [WARN]
|
|
139
|
+
2. 🔒 Security risk: "API_KEY" appears sensitive [SECURITY]
|
|
140
|
+
|
|
141
|
+
✨ All good to go!
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## API Reference
|
|
147
|
+
|
|
148
|
+
### `load(options?)`
|
|
149
|
+
|
|
150
|
+
Simple function to load and validate `.env` file. Returns `{ parsed, warnings, filePath }`.
|
|
151
|
+
|
|
152
|
+
**Parameters:**
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
load({
|
|
156
|
+
path: '.env', // File path
|
|
157
|
+
required: ['DATABASE_URL'], // Required keys
|
|
158
|
+
allowed: null, // Whitelist keys (null = all allowed)
|
|
159
|
+
strict: true, // Throw on unknown keys + empty values
|
|
160
|
+
warnings: true, // Show security/convention warnings
|
|
161
|
+
trim: true, // Trim values
|
|
162
|
+
colorize: true, // Colorize output
|
|
163
|
+
merge: true // Merge into process.env
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Returns:**
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
{
|
|
171
|
+
parsed: { DATABASE_URL: '...', PORT: '3000' }, // Validated env vars
|
|
172
|
+
warnings: [ // Array of warnings
|
|
173
|
+
{ line: 5, key: 'API_KEY', message: '...', severity: 'SECURITY' }
|
|
174
|
+
],
|
|
175
|
+
filePath: '/full/path/.env'
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### `PrettyEnv` Class
|
|
180
|
+
|
|
181
|
+
For advanced usage:
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
const { PrettyEnv } = require('pretty-env');
|
|
185
|
+
|
|
186
|
+
const env = new PrettyEnv({
|
|
187
|
+
required: ['DATABASE_URL'],
|
|
188
|
+
strict: true,
|
|
189
|
+
warnings: true
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Load file
|
|
193
|
+
const result = env.load('.env');
|
|
194
|
+
|
|
195
|
+
// Get specific value with fallback
|
|
196
|
+
const dbUrl = env.get('DATABASE_URL', 'sqlite://db.sqlite');
|
|
197
|
+
|
|
198
|
+
// Get value from parsed env (not process.env)
|
|
199
|
+
console.log(env.env.PORT);
|
|
200
|
+
|
|
201
|
+
// Pretty print loaded variables
|
|
202
|
+
env.print();
|
|
203
|
+
|
|
204
|
+
// Get detailed report
|
|
205
|
+
console.log(env.report());
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Methods:**
|
|
209
|
+
|
|
210
|
+
| Method | Description |
|
|
211
|
+
|--------|-------------|
|
|
212
|
+
| `parse(content)` | Parse raw .env content |
|
|
213
|
+
| `load(filePath)` | Load and validate file |
|
|
214
|
+
| `get(key, default)` | Get value with fallback |
|
|
215
|
+
| `print()` | Pretty print all variables |
|
|
216
|
+
| `report()` | Get detailed report |
|
|
217
|
+
| `validateRequired()` | Check required keys present |
|
|
218
|
+
| `validateAllowed()` | Check only allowed keys exist |
|
|
219
|
+
|
|
220
|
+
### Error Handling
|
|
221
|
+
|
|
222
|
+
```javascript
|
|
223
|
+
const { PrettyEnvError } = require('pretty-env');
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
load({ required: ['DATABASE_URL'] });
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (error instanceof PrettyEnvError) {
|
|
229
|
+
console.error(error.toString());
|
|
230
|
+
// ❌ Failed to parse .env file (1 error)
|
|
231
|
+
// Validation errors:
|
|
232
|
+
// 1. Required key "DATABASE_URL" is missing
|
|
233
|
+
|
|
234
|
+
console.error(error.errors); // Array of detailed errors
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Common Patterns
|
|
242
|
+
|
|
243
|
+
### Best Practice: Require + Validate at Startup
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
// config.js
|
|
247
|
+
const { load } = require('pretty-env');
|
|
248
|
+
|
|
249
|
+
const env = load({
|
|
250
|
+
path: process.env.ENV_FILE || '.env',
|
|
251
|
+
required: [
|
|
252
|
+
'DATABASE_URL',
|
|
253
|
+
'JWT_SECRET',
|
|
254
|
+
'NODE_ENV'
|
|
255
|
+
],
|
|
256
|
+
allowed: [
|
|
257
|
+
'DATABASE_URL', 'JWT_SECRET', 'NODE_ENV', 'PORT',
|
|
258
|
+
'LOG_LEVEL', 'DEBUG', 'API_KEY'
|
|
259
|
+
]
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
module.exports = env.parsed;
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Usage:**
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
const config = require('./config');
|
|
269
|
+
|
|
270
|
+
app.get('/api/users', (req, res) => {
|
|
271
|
+
db.connect(config.DATABASE_URL);
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Development vs Production
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
# .env.example
|
|
279
|
+
DATABASE_URL=
|
|
280
|
+
JWT_SECRET=
|
|
281
|
+
NODE_ENV=
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
# Compare files
|
|
286
|
+
pretty-env compare .env.example .env.production
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Docker with require-env
|
|
290
|
+
|
|
291
|
+
```dockerfile
|
|
292
|
+
FROM node:18
|
|
293
|
+
|
|
294
|
+
WORKDIR /app
|
|
295
|
+
COPY . .
|
|
296
|
+
|
|
297
|
+
# Will fail if .env is missing required keys
|
|
298
|
+
RUN npm run validate:env
|
|
299
|
+
|
|
300
|
+
CMD ["node", "server.js"]
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
{
|
|
305
|
+
"scripts": {
|
|
306
|
+
"validate:env": "pretty-env validate .env"
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Comparison
|
|
314
|
+
|
|
315
|
+
| Feature | pretty-env | dotenv | dotenv-safe | joi-dotenv |
|
|
316
|
+
|---------|-----------|--------|------------|-----------|
|
|
317
|
+
| **Load .env** | ✅ | ✅ | ✅ | ✅ |
|
|
318
|
+
| **Validate format** | ✅ | ❌ | ✅ | ✅ |
|
|
319
|
+
| **Required keys** | ✅ | ❌ | ✅ | ✅ |
|
|
320
|
+
| **Security warnings** | ✅ | ❌ | ❌ | ❌ |
|
|
321
|
+
| **CLI tool** | ✅ | ❌ | ❌ | ❌ |
|
|
322
|
+
| **Compare files** | ✅ | ❌ | ❌ | ❌ |
|
|
323
|
+
| **Zero config** | ✅ | ✅ | ❌ | ❌ |
|
|
324
|
+
| **Size** | ~3 KB | ~2 KB | ~2 KB | ~15 KB |
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Examples
|
|
329
|
+
|
|
330
|
+
### Example 1: Express.js App
|
|
331
|
+
|
|
332
|
+
```javascript
|
|
333
|
+
// app.js
|
|
334
|
+
const express = require('express');
|
|
335
|
+
const { load } = require('pretty-env');
|
|
336
|
+
|
|
337
|
+
// FIRST: Load & validate env
|
|
338
|
+
const env = load({
|
|
339
|
+
required: ['DATABASE_URL', 'JWT_SECRET', 'PORT'],
|
|
340
|
+
warnings: true // Show security warnings during dev
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const app = express();
|
|
344
|
+
|
|
345
|
+
app.get('/health', (req, res) => {
|
|
346
|
+
res.json({ status: 'ok', env: process.env.NODE_ENV });
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
app.listen(env.parsed.PORT, () => {
|
|
350
|
+
console.log(`Server running on port ${env.parsed.PORT}`);
|
|
351
|
+
});
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Example 2: Database Configuration
|
|
355
|
+
|
|
356
|
+
```javascript
|
|
357
|
+
// db.js
|
|
358
|
+
const { PrettyEnv } = require('pretty-env');
|
|
359
|
+
|
|
360
|
+
const db = new PrettyEnv({
|
|
361
|
+
required: ['DATABASE_URL'],
|
|
362
|
+
allowed: ['DATABASE_URL', 'DATABASE_POOL_SIZE', 'DATABASE_LOGGING']
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const config = db.load();
|
|
366
|
+
|
|
367
|
+
module.exports = {
|
|
368
|
+
connectionString: config.parsed.DATABASE_URL,
|
|
369
|
+
max: parseInt(config.parsed.DATABASE_POOL_SIZE || 10),
|
|
370
|
+
logging: config.parsed.DATABASE_LOGGING === 'true'
|
|
371
|
+
};
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Example 3: CI/CD Pipeline
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
#!/bin/bash
|
|
378
|
+
# deploy.sh
|
|
379
|
+
|
|
380
|
+
echo "📋 Validating .env..."
|
|
381
|
+
pretty-env validate .env.production
|
|
382
|
+
|
|
383
|
+
if [ $? -eq 0 ]; then
|
|
384
|
+
echo "✅ Env validation passed"
|
|
385
|
+
npm run build
|
|
386
|
+
npm run deploy
|
|
387
|
+
else
|
|
388
|
+
echo "❌ Env validation failed"
|
|
389
|
+
exit 1
|
|
390
|
+
fi
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Tips & Tricks
|
|
396
|
+
|
|
397
|
+
### Tip 1: Use in Immediate Mode
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
// No need to store in variable
|
|
401
|
+
require('pretty-env').load({
|
|
402
|
+
required: ['DATABASE_URL', 'API_KEY']
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// ... now process.env is populated and validated
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Tip 2: Validate on CI/CD
|
|
409
|
+
|
|
410
|
+
```json
|
|
411
|
+
{
|
|
412
|
+
"scripts": {
|
|
413
|
+
"validate": "pretty-env validate .env.example && pretty-env validate .env",
|
|
414
|
+
"ci": "npm run validate && npm test"
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Tip 3: Compare Before Deployment
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
# Make sure you're not deploying old config
|
|
423
|
+
pretty-env compare .env.example .env.production
|
|
424
|
+
|
|
425
|
+
# Fix any issues
|
|
426
|
+
npm run validate:env
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Tip 4: Development vs CI Differences
|
|
430
|
+
|
|
431
|
+
```javascript
|
|
432
|
+
load({
|
|
433
|
+
warnings: process.env.NODE_ENV === 'development', // Verbose in dev
|
|
434
|
+
strict: process.env.NODE_ENV === 'production' // Strict in prod
|
|
435
|
+
});
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## Security
|
|
441
|
+
|
|
442
|
+
- **Never commit `.env`** — Use `.env.example` instead
|
|
443
|
+
- **Review warnings** — Security warnings highlight risky patterns
|
|
444
|
+
- **Use strong secrets** — Minimum 32 characters for sensitive values
|
|
445
|
+
- **Rotate regularly** — Treat API keys like passwords
|
|
446
|
+
|
|
447
|
+
### What pretty-env Does NOT Do
|
|
448
|
+
|
|
449
|
+
- 🚫 Does not encrypt values
|
|
450
|
+
- 🚫 Does not hide values in logs
|
|
451
|
+
- 🚫 Does not transmit data
|
|
452
|
+
|
|
453
|
+
For encryption, use tools like [dotenv-vault](https://www.dotenv.org/docs/security/dotenv-vault) alongside pretty-env!
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## FAQs
|
|
458
|
+
|
|
459
|
+
**Q: Is pretty-env required for projects using dotenv?**
|
|
460
|
+
A: No, but we recommend it for catching configuration errors early. Works great alongside dotenv!
|
|
461
|
+
|
|
462
|
+
**Q: Does it support `.env.local`, `.env.production`, etc?**
|
|
463
|
+
A: Yes! Pass the path: `load({ path: '.env.production' })`
|
|
464
|
+
|
|
465
|
+
**Q: Can I use pretty-env in monorepos?**
|
|
466
|
+
A: Yes! Each package can have its own config validation.
|
|
467
|
+
|
|
468
|
+
**Q: Performance impact?**
|
|
469
|
+
A: Negligible (~1-2ms per load). Safe to call at startup.
|
|
470
|
+
|
|
471
|
+
**Q: TypeScript support?**
|
|
472
|
+
A: Full JSDoc types. TypeScript support coming in v2.
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
## Contributing
|
|
477
|
+
|
|
478
|
+
Contributions welcome!
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
git clone https://github.com/likhithsp/pretty-env.git
|
|
482
|
+
cd pretty-env
|
|
483
|
+
npm install
|
|
484
|
+
npm test
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
## License
|
|
490
|
+
|
|
491
|
+
MIT © 2026 Likhith SP
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
**Made with ❤️ to catch .env bugs before production**
|
|
496
|
+
|
|
497
|
+
[Star on GitHub](https://github.com/likhithsp/pretty-env) • [NPM Package](https://www.npmjs.com/package/pretty-env) • [Issues](https://github.com/likhithsp/pretty-env/issues)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const { PrettyEnv } = require('../src/index.js');
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const command = args[0] || 'validate';
|
|
8
|
+
const envFile = args[1] || '.env';
|
|
9
|
+
|
|
10
|
+
const colors = {
|
|
11
|
+
reset: '\x1b[0m',
|
|
12
|
+
bright: '\x1b[1m',
|
|
13
|
+
dim: '\x1b[2m',
|
|
14
|
+
red: '\x1b[31m',
|
|
15
|
+
green: '\x1b[32m',
|
|
16
|
+
yellow: '\x1b[33m',
|
|
17
|
+
cyan: '\x1b[36m',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function colorize(text, color) {
|
|
21
|
+
return `${colors[color] || ''}${text}${colors.reset}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function printHeader(title) {
|
|
25
|
+
console.log(`\n${colorize(title, 'bright')}`);
|
|
26
|
+
console.log(colorize('─'.repeat(title.length), 'dim'));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function validate() {
|
|
30
|
+
const env = new PrettyEnv();
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
console.log(colorize(` 🔍 Validating ${envFile}...`, 'cyan'));
|
|
34
|
+
|
|
35
|
+
if (!fs.existsSync(envFile)) {
|
|
36
|
+
console.error(colorize(` ❌ File not found: ${envFile}`, 'red'));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const result = env.load(envFile);
|
|
41
|
+
|
|
42
|
+
printHeader(` ✅ Valid! Loaded ${result.parsed ? Object.keys(result.parsed).length : 0} variables`);
|
|
43
|
+
|
|
44
|
+
console.log(colorize('\n📋 Variables:', 'bright'));
|
|
45
|
+
env.print();
|
|
46
|
+
|
|
47
|
+
if (env.warnings.length > 0) {
|
|
48
|
+
printHeader(` ⚠️ Warnings (${env.warnings.length})`);
|
|
49
|
+
env.warnings.forEach((warn, i) => {
|
|
50
|
+
const severity = warn.risk === 'CRITICAL' ? colorize(` [${warn.risk}]`, 'red') :
|
|
51
|
+
warn.risk === 'HIGH' ? colorize(` [${warn.risk}]`, 'red') :
|
|
52
|
+
warn.severity === 'SECURITY' ? colorize(' [SECURITY]', 'red') :
|
|
53
|
+
warn.severity === 'WARN' ? colorize(' [WARN]', 'yellow') :
|
|
54
|
+
colorize(' [INFO]', 'dim');
|
|
55
|
+
console.log(` ${i + 1}. ${warn.message}${severity}`);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(colorize('\n✨ All good to go!', 'green'));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error(colorize('\n❌ Validation failed!', 'red'));
|
|
62
|
+
if (error.errors) {
|
|
63
|
+
error.errors.forEach((err, i) => {
|
|
64
|
+
console.error(colorize(` ${i + 1}. Line ${err.line}: ${err.message}`, 'red'));
|
|
65
|
+
});
|
|
66
|
+
} else {
|
|
67
|
+
console.error(colorize(` ${error.message}`, 'red'));
|
|
68
|
+
}
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function compare() {
|
|
74
|
+
if (args.length < 3) {
|
|
75
|
+
console.error(colorize('Usage: pretty-env compare <file1> <file2>', 'red'));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const file1 = args[1];
|
|
80
|
+
const file2 = args[2];
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const env1 = new PrettyEnv().load(file1).parsed;
|
|
84
|
+
const env2 = new PrettyEnv().load(file2).parsed;
|
|
85
|
+
|
|
86
|
+
const keys1 = new Set(Object.keys(env1));
|
|
87
|
+
const keys2 = new Set(Object.keys(env2));
|
|
88
|
+
|
|
89
|
+
const only1 = [...keys1].filter(k => !keys2.has(k));
|
|
90
|
+
const only2 = [...keys2].filter(k => !keys1.has(k));
|
|
91
|
+
const different = [...keys1].filter(k => keys2.has(k) && env1[k] !== env2[k]);
|
|
92
|
+
|
|
93
|
+
printHeader(` 🔍 Comparing ${file1} ↔ ${file2}`);
|
|
94
|
+
|
|
95
|
+
if (only1.length > 0) {
|
|
96
|
+
console.log(colorize(`\n📌 Only in ${file1}:`, 'yellow'));
|
|
97
|
+
only1.forEach(k => console.log(` • ${k}`));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (only2.length > 0) {
|
|
101
|
+
console.log(colorize(`\n📌 Only in ${file2}:`, 'yellow'));
|
|
102
|
+
only2.forEach(k => console.log(` • ${k}`));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (different.length > 0) {
|
|
106
|
+
console.log(colorize('\n📌 Different values:', 'yellow'));
|
|
107
|
+
different.forEach(k => {
|
|
108
|
+
console.log(` ${k}:`);
|
|
109
|
+
console.log(` ${file1}: ${env1[k]}`);
|
|
110
|
+
console.log(` ${file2}: ${env2[k]}`);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (only1.length === 0 && only2.length === 0 && different.length === 0) {
|
|
115
|
+
console.log(colorize('\n✅ Files are identical!', 'green'));
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error(colorize(`\n❌ Error: ${error.message}`, 'red'));
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function help() {
|
|
124
|
+
console.log(`
|
|
125
|
+
${colorize('pretty-env', 'bright')} - Validate and prettify .env files
|
|
126
|
+
|
|
127
|
+
${colorize('Usage:', 'bright')}
|
|
128
|
+
pretty-env [command] [options]
|
|
129
|
+
|
|
130
|
+
${colorize('Commands:', 'bright')}
|
|
131
|
+
validate [file] Validate .env file (default: .env)
|
|
132
|
+
compare <f1> <f2> Compare two .env files
|
|
133
|
+
help Show this help message
|
|
134
|
+
|
|
135
|
+
${colorize('Examples:', 'bright')}
|
|
136
|
+
$ pretty-env validate
|
|
137
|
+
$ pretty-env validate .env.production
|
|
138
|
+
$ pretty-env compare .env.example .env
|
|
139
|
+
$ pretty-env help
|
|
140
|
+
`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Run command
|
|
144
|
+
if (command === 'validate') {
|
|
145
|
+
validate();
|
|
146
|
+
} else if (command === 'compare') {
|
|
147
|
+
compare();
|
|
148
|
+
} else if (command === 'help' || command === '--help' || command === '-h') {
|
|
149
|
+
help();
|
|
150
|
+
} else {
|
|
151
|
+
console.error(colorize(`Unknown command: ${command}`, 'red'));
|
|
152
|
+
help();
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pretty-env",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Validates and prettifies .env files with clear error messages. Warns on missing keys, typos, or insecure values. Drop-in replacement for dotenv.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"pretty-env": "cli/pretty-env-cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "jest",
|
|
11
|
+
"test:watch": "jest --watch",
|
|
12
|
+
"test:coverage": "jest --coverage",
|
|
13
|
+
"lint": "eslint src/ cli/ tests/",
|
|
14
|
+
"format": "prettier --write src/ cli/ tests/ examples/",
|
|
15
|
+
"validate": "node cli/pretty-env-cli.js validate",
|
|
16
|
+
"validate:bad": "node cli/pretty-env-cli.js validate examples/.env.bad",
|
|
17
|
+
"example": "node examples/usage.js",
|
|
18
|
+
"prepublishOnly": "npm test && npm run lint",
|
|
19
|
+
"prepare": "npm test"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"env",
|
|
23
|
+
"environment",
|
|
24
|
+
"variables",
|
|
25
|
+
".env",
|
|
26
|
+
"validation",
|
|
27
|
+
"dotenv",
|
|
28
|
+
"config",
|
|
29
|
+
"prettify",
|
|
30
|
+
"format",
|
|
31
|
+
"security",
|
|
32
|
+
"cli",
|
|
33
|
+
"zero-config"
|
|
34
|
+
],
|
|
35
|
+
"author": "Likhith SP",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/likhithsp/pretty-env.git"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/likhithsp/pretty-env/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/likhithsp/pretty-env#readme",
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=12.0.0"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"src/",
|
|
50
|
+
"cli/",
|
|
51
|
+
"README.md",
|
|
52
|
+
"LICENSE",
|
|
53
|
+
"CHANGELOG.md"
|
|
54
|
+
],
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"jest": "^29.0.0",
|
|
57
|
+
"eslint": "^8.0.0",
|
|
58
|
+
"prettier": "^3.0.0"
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const SECURITY_PATTERNS = {
|
|
5
|
+
password: { pattern: /(password|pwd|secret|api.?key|token|auth)/i, risk: 'HIGH' },
|
|
6
|
+
url: { pattern: /(url|host|server|endpoint)/i, risk: 'MEDIUM' },
|
|
7
|
+
email: { pattern: /email/i, risk: 'LOW' },
|
|
8
|
+
port: { pattern: /port/i, risk: 'LOW' }
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const WARNING_RULES = {
|
|
12
|
+
allCaps: (key) => /^[A-Z_]+$/.test(key),
|
|
13
|
+
shortKey: (key) => key.length < 3,
|
|
14
|
+
underscorePrefix: (key) => key.startsWith('_'),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
class PrettyEnvError extends Error {
|
|
18
|
+
constructor(message, errors = []) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'PrettyEnvError';
|
|
21
|
+
this.errors = errors;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
toString() {
|
|
25
|
+
let output = `\n❌ ${this.message}\n`;
|
|
26
|
+
|
|
27
|
+
if (this.errors.length > 0) {
|
|
28
|
+
output += '\nValidation errors:\n';
|
|
29
|
+
this.errors.forEach((err, i) => {
|
|
30
|
+
output += ` ${i + 1}. ${err.line ? `Line ${err.line}: ` : ''}${err.message}\n`;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return output;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class PrettyEnv {
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
this.options = {
|
|
41
|
+
strict: options.strict !== false,
|
|
42
|
+
required: options.required || [],
|
|
43
|
+
allowed: options.allowed || null,
|
|
44
|
+
warnings: options.warnings !== false,
|
|
45
|
+
trim: options.trim !== false,
|
|
46
|
+
colorize: options.colorize !== false,
|
|
47
|
+
...options
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
this.warnings = [];
|
|
51
|
+
this.env = {};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parse .env file content
|
|
56
|
+
*/
|
|
57
|
+
parse(content) {
|
|
58
|
+
const lines = content.split('\n');
|
|
59
|
+
const parsed = {};
|
|
60
|
+
const errors = [];
|
|
61
|
+
|
|
62
|
+
lines.forEach((line, index) => {
|
|
63
|
+
const lineNum = index + 1;
|
|
64
|
+
const trimmed = line.trim();
|
|
65
|
+
|
|
66
|
+
// Skip empty lines and comments
|
|
67
|
+
if (!trimmed || trimmed.startsWith('#')) return;
|
|
68
|
+
|
|
69
|
+
// Check for valid KEY=VALUE format
|
|
70
|
+
if (!trimmed.includes('=')) {
|
|
71
|
+
errors.push({
|
|
72
|
+
line: lineNum,
|
|
73
|
+
message: `Invalid format: "${trimmed}". Expected KEY=VALUE`,
|
|
74
|
+
key: trimmed
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const [key, ...valueParts] = trimmed.split('=');
|
|
80
|
+
const rawKey = key.trim();
|
|
81
|
+
let value = valueParts.join('=').trim();
|
|
82
|
+
|
|
83
|
+
// Validate key
|
|
84
|
+
if (!rawKey) {
|
|
85
|
+
errors.push({
|
|
86
|
+
line: lineNum,
|
|
87
|
+
message: 'Empty key name',
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!/^[A-Z_][A-Z0-9_]*$/i.test(rawKey)) {
|
|
93
|
+
errors.push({
|
|
94
|
+
line: lineNum,
|
|
95
|
+
message: `Invalid key name: "${rawKey}". Must start with letter or underscore, contain only alphanumeric and underscores`,
|
|
96
|
+
key: rawKey
|
|
97
|
+
});
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Remove quotes if present
|
|
102
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
103
|
+
(value.startsWith('\'') && value.endsWith('\''))) {
|
|
104
|
+
value = value.slice(1, -1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check for empty value
|
|
108
|
+
if (value === '' && this.options.strict) {
|
|
109
|
+
this.warnings.push({
|
|
110
|
+
line: lineNum,
|
|
111
|
+
message: `Empty value for key "${rawKey}"`,
|
|
112
|
+
key: rawKey,
|
|
113
|
+
severity: 'WARN'
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Check for trailing spaces
|
|
118
|
+
if (line.trim() !== line && this.options.warnings) {
|
|
119
|
+
this.warnings.push({
|
|
120
|
+
line: lineNum,
|
|
121
|
+
message: 'Trailing whitespace detected',
|
|
122
|
+
key: rawKey,
|
|
123
|
+
severity: 'INFO'
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.checkSecurityWarnings(rawKey, value, lineNum);
|
|
128
|
+
this.checkKeyWarnings(rawKey, lineNum);
|
|
129
|
+
|
|
130
|
+
parsed[rawKey] = value;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (errors.length > 0) {
|
|
134
|
+
throw new PrettyEnvError(
|
|
135
|
+
`Failed to parse .env file (${errors.length} error${errors.length !== 1 ? 's' : ''})`,
|
|
136
|
+
errors
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
this.env = parsed;
|
|
141
|
+
return parsed;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Check security-related warnings
|
|
146
|
+
*/
|
|
147
|
+
checkSecurityWarnings(key, value, lineNum) {
|
|
148
|
+
for (const [type, config] of Object.entries(SECURITY_PATTERNS)) {
|
|
149
|
+
if (config.pattern.test(key)) {
|
|
150
|
+
if (!value || value.length < 8) {
|
|
151
|
+
this.warnings.push({
|
|
152
|
+
line: lineNum,
|
|
153
|
+
message: `🔒 Security risk: "${key}" appears sensitive (${type}) but value is weak or empty`,
|
|
154
|
+
key,
|
|
155
|
+
severity: 'SECURITY',
|
|
156
|
+
risk: config.risk
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Check for common weak values
|
|
161
|
+
if (value.match(/^(test|demo|123|password|secret|admin)/i)) {
|
|
162
|
+
this.warnings.push({
|
|
163
|
+
line: lineNum,
|
|
164
|
+
message: `⚠️ Insecure value detected in "${key}". Never commit real credentials!`,
|
|
165
|
+
key,
|
|
166
|
+
severity: 'SECURITY',
|
|
167
|
+
risk: 'CRITICAL'
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Check general key naming warnings
|
|
177
|
+
*/
|
|
178
|
+
checkKeyWarnings(key, lineNum) {
|
|
179
|
+
if (!this.options.warnings) return;
|
|
180
|
+
|
|
181
|
+
for (const [rule, check] of Object.entries(WARNING_RULES)) {
|
|
182
|
+
if (check(key)) {
|
|
183
|
+
let message = '';
|
|
184
|
+
switch (rule) {
|
|
185
|
+
case 'allCaps':
|
|
186
|
+
message = `Convention: Variables should use UPPER_SNAKE_CASE. "${key}" is already good!`;
|
|
187
|
+
break;
|
|
188
|
+
case 'shortKey':
|
|
189
|
+
message = `Convention: Key "${key}" is very short. Consider more descriptive names for clarity.`;
|
|
190
|
+
break;
|
|
191
|
+
case 'underscorePrefix':
|
|
192
|
+
message = 'Convention: Key starts with "_". Reserve for private/internal variables.';
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
if (message) {
|
|
196
|
+
this.warnings.push({
|
|
197
|
+
line: lineNum,
|
|
198
|
+
message,
|
|
199
|
+
key,
|
|
200
|
+
severity: 'INFO'
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Validate required keys
|
|
209
|
+
*/
|
|
210
|
+
validateRequired() {
|
|
211
|
+
const missing = this.options.required.filter(key => !(key in this.env));
|
|
212
|
+
|
|
213
|
+
if (missing.length > 0) {
|
|
214
|
+
throw new PrettyEnvError(
|
|
215
|
+
`Missing required keys: ${missing.join(', ')}`,
|
|
216
|
+
missing.map(key => ({
|
|
217
|
+
message: `Required key "${key}" is missing`,
|
|
218
|
+
key
|
|
219
|
+
}))
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Validate allowed keys only
|
|
226
|
+
*/
|
|
227
|
+
validateAllowed() {
|
|
228
|
+
if (!this.options.allowed) return;
|
|
229
|
+
|
|
230
|
+
const notAllowed = Object.keys(this.env).filter(key => !this.options.allowed.includes(key));
|
|
231
|
+
|
|
232
|
+
if (notAllowed.length > 0 && this.options.strict) {
|
|
233
|
+
throw new PrettyEnvError(
|
|
234
|
+
`Unknown keys found: ${notAllowed.join(', ')}`,
|
|
235
|
+
notAllowed.map(key => ({
|
|
236
|
+
message: `Key "${key}" is not in allowed list`,
|
|
237
|
+
key
|
|
238
|
+
}))
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Load and validate .env file
|
|
245
|
+
*/
|
|
246
|
+
load(filePath = '.env') {
|
|
247
|
+
try {
|
|
248
|
+
const fullPath = path.resolve(filePath);
|
|
249
|
+
|
|
250
|
+
if (!fs.existsSync(fullPath)) {
|
|
251
|
+
throw new PrettyEnvError(`File not found: ${filePath}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
255
|
+
this.parse(content);
|
|
256
|
+
|
|
257
|
+
this.validateRequired();
|
|
258
|
+
this.validateAllowed();
|
|
259
|
+
|
|
260
|
+
// Merge into process.env if requested
|
|
261
|
+
if (this.options.merge !== false) {
|
|
262
|
+
Object.assign(process.env, this.env);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
parsed: this.env,
|
|
267
|
+
warnings: this.options.warnings ? this.warnings : [],
|
|
268
|
+
filePath: fullPath
|
|
269
|
+
};
|
|
270
|
+
} catch (error) {
|
|
271
|
+
if (error instanceof PrettyEnvError) throw error;
|
|
272
|
+
throw new PrettyEnvError(`Failed to load ${filePath}: ${error.message}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get value with fallback
|
|
278
|
+
*/
|
|
279
|
+
get(key, defaultValue = undefined) {
|
|
280
|
+
return this.env[key] ?? defaultValue ?? process.env[key];
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Pretty print loaded env with colors
|
|
285
|
+
*/
|
|
286
|
+
print() {
|
|
287
|
+
if (Object.keys(this.env).length === 0) {
|
|
288
|
+
console.log(' (no environment variables loaded)');
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const maxKeyLength = Math.max(...Object.keys(this.env).map(k => k.length));
|
|
293
|
+
|
|
294
|
+
Object.entries(this.env).forEach(([key, value]) => {
|
|
295
|
+
const displayValue = value.length > 50 ? value.substring(0, 47) + '...' : value;
|
|
296
|
+
console.log(` ${key.padEnd(maxKeyLength)} = ${displayValue}`);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Get formatted report
|
|
302
|
+
*/
|
|
303
|
+
report() {
|
|
304
|
+
const report = {
|
|
305
|
+
loaded: Object.keys(this.env).length,
|
|
306
|
+
warnings: this.warnings.length,
|
|
307
|
+
errors: [],
|
|
308
|
+
variables: this.env
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
if (this.warnings.length > 0) {
|
|
312
|
+
report.warningDetails = this.warnings;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return report;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Simple one-line usage: require('pretty-env').load()
|
|
321
|
+
*/
|
|
322
|
+
function load(options = {}) {
|
|
323
|
+
const env = new PrettyEnv(options);
|
|
324
|
+
return env.load(options.path || '.env');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
module.exports = {
|
|
328
|
+
PrettyEnv,
|
|
329
|
+
load,
|
|
330
|
+
PrettyEnvError
|
|
331
|
+
};
|