ultraenv 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 +2058 -0
- package/bin/ultraenv.mjs +3 -0
- package/dist/chunk-2USZPWLZ.js +288 -0
- package/dist/chunk-3UV2QNJL.js +270 -0
- package/dist/chunk-3VYXPTYV.js +179 -0
- package/dist/chunk-4XUYMRK5.js +366 -0
- package/dist/chunk-5G2DU52U.js +189 -0
- package/dist/chunk-6KS56D6E.js +172 -0
- package/dist/chunk-AWN6ADV7.js +328 -0
- package/dist/chunk-CHVO6NWI.js +203 -0
- package/dist/chunk-CIFMBJ4H.js +3975 -0
- package/dist/chunk-GC7RXHLA.js +253 -0
- package/dist/chunk-HFXQGJY3.js +445 -0
- package/dist/chunk-IGFVP24Q.js +91 -0
- package/dist/chunk-IKPTKALB.js +78 -0
- package/dist/chunk-JB7RKV3C.js +66 -0
- package/dist/chunk-MNVFG7H4.js +611 -0
- package/dist/chunk-MSXMESFP.js +1910 -0
- package/dist/chunk-N5PAV4NM.js +127 -0
- package/dist/chunk-NBOABPHM.js +158 -0
- package/dist/chunk-OMAOROL4.js +49 -0
- package/dist/chunk-R7PZRSZ7.js +105 -0
- package/dist/chunk-TE7HPLA6.js +73 -0
- package/dist/chunk-TMT5KCO3.js +101 -0
- package/dist/chunk-UEWYFN6A.js +189 -0
- package/dist/chunk-WMHN5RW2.js +128 -0
- package/dist/chunk-XC65ORJ5.js +70 -0
- package/dist/chunk-YMMP4VQL.js +118 -0
- package/dist/chunk-YN2KGTCB.js +33 -0
- package/dist/chunk-YTICOB5M.js +65 -0
- package/dist/chunk-YVWLXFUT.js +107 -0
- package/dist/ci-check-sync-VBMSVWIV.js +48 -0
- package/dist/ci-scan-24MT5XGS.js +41 -0
- package/dist/ci-setup-C2NKEFRD.js +135 -0
- package/dist/ci-validate-7AW24LSQ.js +57 -0
- package/dist/cli/index.cjs +9217 -0
- package/dist/cli/index.d.cts +9 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +339 -0
- package/dist/comparator-RDKX3OI7.js +13 -0
- package/dist/completion-MW35C2XO.js +168 -0
- package/dist/config-O5YRQP5Z.js +13 -0
- package/dist/debug-PTPXAF3K.js +131 -0
- package/dist/declaration-LEME4AFZ.js +10 -0
- package/dist/doctor-FZAUPKHS.js +129 -0
- package/dist/envs-compare-5K3HESX5.js +49 -0
- package/dist/envs-create-2XXHXMGA.js +58 -0
- package/dist/envs-list-NQM5252B.js +59 -0
- package/dist/envs-switch-6L2AQYID.js +50 -0
- package/dist/envs-validate-FL73Q76T.js +89 -0
- package/dist/fs-VH7ATUS3.js +31 -0
- package/dist/generator-LFZBMZZS.js +14 -0
- package/dist/git-BZS4DPAI.js +30 -0
- package/dist/help-3XJBXEHE.js +121 -0
- package/dist/index.cjs +12907 -0
- package/dist/index.d.cts +2562 -0
- package/dist/index.d.ts +2562 -0
- package/dist/index.js +3212 -0
- package/dist/init-Y7JQ2KYJ.js +146 -0
- package/dist/install-hook-SKXIV6NV.js +111 -0
- package/dist/json-schema-I26YNQBH.js +10 -0
- package/dist/key-manager-O3G55WPU.js +25 -0
- package/dist/middleware/express.cjs +103 -0
- package/dist/middleware/express.d.cts +115 -0
- package/dist/middleware/express.d.ts +115 -0
- package/dist/middleware/express.js +8 -0
- package/dist/middleware/fastify.cjs +91 -0
- package/dist/middleware/fastify.d.cts +111 -0
- package/dist/middleware/fastify.d.ts +111 -0
- package/dist/middleware/fastify.js +8 -0
- package/dist/module-IDIZPP4M.js +10 -0
- package/dist/protect-NCWPM6VC.js +161 -0
- package/dist/scan-TRLY36TT.js +58 -0
- package/dist/schema/index.cjs +4074 -0
- package/dist/schema/index.d.cts +1244 -0
- package/dist/schema/index.d.ts +1244 -0
- package/dist/schema/index.js +152 -0
- package/dist/sync-TMHMTLH2.js +186 -0
- package/dist/typegen-SQOSXBWM.js +80 -0
- package/dist/validate-IOAM5HWS.js +100 -0
- package/dist/vault-decrypt-U6HJZNBV.js +111 -0
- package/dist/vault-diff-B3ZOQTWI.js +132 -0
- package/dist/vault-encrypt-GUSLCSKS.js +112 -0
- package/dist/vault-init-GUBOTOUL.js +106 -0
- package/dist/vault-rekey-DAHT7JCN.js +132 -0
- package/dist/vault-status-GDLRU2OK.js +90 -0
- package/dist/vault-verify-CD76FJSF.js +102 -0
- package/package.json +106 -0
package/README.md
ADDED
|
@@ -0,0 +1,2058 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
5
|
+
โ โ
|
|
6
|
+
โ โโโโโโโ โโโโโโโ โโโโโโโโโโโ โโโโโโ โโโ โ
|
|
7
|
+
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโ โโโโ โ
|
|
8
|
+
โ โโโ โโโ โโโโโโโโโโโโโโโโโโ โโโโโโโ โ
|
|
9
|
+
โ โโโ โโโ โโโโโโโโโโโโโโโโโโ โโโโโโโ โ
|
|
10
|
+
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโ โโโ โ
|
|
11
|
+
โ โโโโโโโ โโโโโโโ โโโโโโโโโโโ โโโโโโ โโโ โ
|
|
12
|
+
โ โ
|
|
13
|
+
โ The Ultimate Environment Variable Manager โ
|
|
14
|
+
โ v1.0.0 โ
|
|
15
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Validate, Type, Encrypt, Sync, and Never Ship Broken Configs Again.**
|
|
19
|
+
|
|
20
|
+
[](https://www.npmjs.com/package/ultraenv)
|
|
21
|
+
[](https://github.com/Avinashvelu03/ultraenv/blob/main/LICENSE)
|
|
22
|
+
[](https://nodejs.org)
|
|
23
|
+
[](https://www.npmjs.com/package/ultraenv)
|
|
24
|
+
[](https://www.typescriptlang.org/)
|
|
25
|
+
[](https://github.com/Avinashvelu03/ultraenv/actions)
|
|
26
|
+
|
|
27
|
+
[Getting Started](#-quick-start) ยท [Schema Reference](#-schema-reference) ยท [CLI Reference](#-cli-command-reference) ยท [Vault Guide](#-encryption--vault) ยท [Docs](#documentation)
|
|
28
|
+
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## ๐ค Why ultraenv?
|
|
34
|
+
|
|
35
|
+
Every project uses environment variables. Every project gets them wrong eventually.
|
|
36
|
+
|
|
37
|
+
- **Missing variables** crash production at 3 AM.
|
|
38
|
+
- **Wrong types** (`process.env.PORT` is always a string) cause silent bugs.
|
|
39
|
+
- **Leaked secrets** in `.env` files end up in git history forever.
|
|
40
|
+
- **Drifting `.env.example`** files lead to confusing onboarding for new developers.
|
|
41
|
+
- **No validation** means you find out about missing configs at runtime.
|
|
42
|
+
|
|
43
|
+
**ultraenv** solves all of these problems with a single, zero-dependency library that provides:
|
|
44
|
+
|
|
45
|
+
| Problem | ultraenv Solution |
|
|
46
|
+
|---|---|
|
|
47
|
+
| No type safety for `process.env` | Full TypeScript inference from schema |
|
|
48
|
+
| Secrets leaked in git | Built-in secret scanner with 55+ patterns |
|
|
49
|
+
| No `.env` validation | Schema engine with 30+ validators |
|
|
50
|
+
| Secrets in plain text | AES-256-GCM encrypted vault |
|
|
51
|
+
| `.env.example` out of sync | Auto-sync with watch mode |
|
|
52
|
+
| No multi-environment support | Multi-env management (dev, staging, prod) |
|
|
53
|
+
| Can't use in CI/CD | CI commands with SARIF output |
|
|
54
|
+
| Hard to migrate from dotenv | Drop-in replacement with `load()` |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## ๐ Feature Comparison
|
|
59
|
+
|
|
60
|
+
| Feature | **ultraenv** | [dotenv](https://github.com/motdotla/dotenv) | [envalid](https://github.com/af/envalid) | [@t3-oss/env](https://github.com/t3-oss/env-core) |
|
|
61
|
+
|---|:---:|:---:|:---:|:---:|
|
|
62
|
+
| **Parse `.env` files** | โ
| โ
| โ
| โ
|
|
|
63
|
+
| **TypeScript inference** | โ
Full | โ | โ
Partial | โ
Full |
|
|
64
|
+
| **Schema validators** | โ
30+ | โ | โ
8 | โ
Via zod |
|
|
65
|
+
| **String validators** | โ
20+ | โ | โ | Via zod |
|
|
66
|
+
| **Secret scanning** | โ
55+ patterns | โ | โ | โ |
|
|
67
|
+
| **Encrypted vault** | โ
AES-256-GCM | โ | โ | โ |
|
|
68
|
+
| **Key rotation** | โ
| โ | โ | โ |
|
|
69
|
+
| **`.env.example` sync** | โ
Watch mode | โ | โ | โ |
|
|
70
|
+
| **Type generation** | โ
`.d.ts` / module / JSON Schema | โ | โ | โ
|
|
|
71
|
+
| **Multi-environment** | โ
11 file variants | โ | โ | โ |
|
|
72
|
+
| **Framework presets** | โ
9 presets | โ | โ | โ |
|
|
73
|
+
| **CI/CD integration** | โ
SARIF output | โ | โ | โ |
|
|
74
|
+
| **Variable interpolation** | โ
`$VAR` / `${VAR}` | โ
| โ | โ |
|
|
75
|
+
| **File cascade** | โ
Priority-based | โ | โ | โ |
|
|
76
|
+
| **Hot reload watcher** | โ
| โ | โ | โ |
|
|
77
|
+
| **Health check API** | โ
| โ | โ | โ |
|
|
78
|
+
| **Express middleware** | โ
| โ | โ | โ |
|
|
79
|
+
| **Fastify plugin** | โ
| โ | โ | โ |
|
|
80
|
+
| **SARIF output** | โ
| โ | โ | โ |
|
|
81
|
+
| **Git hook integration** | โ
| โ | โ | โ |
|
|
82
|
+
| **dotenv-compatible API** | โ
| โ | โ | โ |
|
|
83
|
+
| **Zero dependencies** | โ
| โ
| โ | โ |
|
|
84
|
+
| **Node.js** | โฅ 18 | โฅ 12 | โฅ 14 | โฅ 18 |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## ๐ Quick Start
|
|
89
|
+
|
|
90
|
+
Get started in three steps:
|
|
91
|
+
|
|
92
|
+
### Step 1 โ Install
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm install ultraenv
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Step 2 โ Define your schema
|
|
99
|
+
|
|
100
|
+
Create an `env.ts` file:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { defineEnv, t } from 'ultraenv';
|
|
104
|
+
|
|
105
|
+
const env = defineEnv({
|
|
106
|
+
// Required string with URL validation
|
|
107
|
+
DATABASE_URL: t.string().format('url').required(),
|
|
108
|
+
|
|
109
|
+
// Number with port validation and a default
|
|
110
|
+
PORT: t.number().port().default(3000),
|
|
111
|
+
|
|
112
|
+
// Enum with literal union types
|
|
113
|
+
NODE_ENV: t.enum(['development', 'staging', 'production'] as const).required(),
|
|
114
|
+
|
|
115
|
+
// Boolean with default
|
|
116
|
+
DEBUG: t.boolean().default(false),
|
|
117
|
+
|
|
118
|
+
// Optional email
|
|
119
|
+
ADMIN_EMAIL: t.email().optional(),
|
|
120
|
+
|
|
121
|
+
// Array with custom separator
|
|
122
|
+
ALLOWED_ORIGINS: t.array().separator(';').default(['http://localhost:3000']),
|
|
123
|
+
|
|
124
|
+
// Duration string
|
|
125
|
+
CACHE_TTL: t.duration().default('1h'),
|
|
126
|
+
|
|
127
|
+
// Bytes string
|
|
128
|
+
MAX_UPLOAD_SIZE: t.bytes().default('10MB'),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export default env;
|
|
132
|
+
|
|
133
|
+
// TypeScript knows the exact types:
|
|
134
|
+
// env.DATABASE_URL โ string
|
|
135
|
+
// env.PORT โ number (not string!)
|
|
136
|
+
// env.NODE_ENV โ 'development' | 'staging' | 'production'
|
|
137
|
+
// env.DEBUG โ boolean
|
|
138
|
+
// env.ADMIN_EMAIL โ string | undefined
|
|
139
|
+
// env.ALLOWED_ORIGINS โ readonly string[]
|
|
140
|
+
// env.CACHE_TTL โ string
|
|
141
|
+
// env.MAX_UPLOAD_SIZE โ string
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Step 3 โ Use your typed env everywhere
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// Any file in your project:
|
|
148
|
+
import env from './env';
|
|
149
|
+
|
|
150
|
+
// Fully typed โ no more `process.env.PORT as unknown as number`
|
|
151
|
+
const server = createServer({
|
|
152
|
+
port: env.PORT, // number
|
|
153
|
+
host: env.HOST, // string
|
|
154
|
+
databaseUrl: env.DATABASE_URL, // string (URL-validated)
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (env.NODE_ENV === 'development') {
|
|
158
|
+
// TypeScript knows the exact enum values!
|
|
159
|
+
console.log('Development mode:', env.DEBUG);
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
That's it. Your environment is validated, typed, and safe.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## ๐ฆ Installation
|
|
168
|
+
|
|
169
|
+
### npm
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
npm install ultraenv
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### pnpm
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
pnpm add ultraenv
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### yarn
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
yarn add ultraenv
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### bun
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
bun add ultraenv
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Global CLI (optional)
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
npm install -g ultraenv
|
|
197
|
+
|
|
198
|
+
# Then use the CLI anywhere
|
|
199
|
+
ultraenv init
|
|
200
|
+
ultraenv validate
|
|
201
|
+
ultraenv scan
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Verify Installation
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
npx ultraenv --version
|
|
208
|
+
# โ ultraenv v1.0.0
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## ๐ง CLI Command Reference
|
|
214
|
+
|
|
215
|
+
ultraenv ships with a powerful CLI for managing your environment files from the terminal.
|
|
216
|
+
|
|
217
|
+
### Core Commands
|
|
218
|
+
|
|
219
|
+
| Command | Description |
|
|
220
|
+
|---|---|
|
|
221
|
+
| `ultraenv init` | Initialize project with config, `.env`, `.env.example`, and `.gitignore` |
|
|
222
|
+
| `ultraenv validate` | Load and validate environment variables |
|
|
223
|
+
| `ultraenv typegen` | Generate TypeScript types from schema |
|
|
224
|
+
| `ultraenv sync` | Sync `.env.example` with `.env` (check, generate, interactive, watch modes) |
|
|
225
|
+
| `ultraenv scan` | Scan codebase for leaked secrets |
|
|
226
|
+
| `ultraenv debug` | Show diagnostics and debugging info |
|
|
227
|
+
| `ultraenv protect` | Check and update `.gitignore` for secret protection |
|
|
228
|
+
| `ultraenv doctor` | Run self-checks and diagnose common issues |
|
|
229
|
+
| `ultraenv help` | Show help for any command |
|
|
230
|
+
|
|
231
|
+
### Vault Commands
|
|
232
|
+
|
|
233
|
+
| Command | Description |
|
|
234
|
+
|---|---|
|
|
235
|
+
| `ultraenv vault init` | Initialize encrypted vault |
|
|
236
|
+
| `ultraenv vault encrypt` | Encrypt environment to vault |
|
|
237
|
+
| `ultraenv vault decrypt` | Decrypt environment from vault |
|
|
238
|
+
| `ultraenv vault rekey` | Rotate encryption keys |
|
|
239
|
+
| `ultraenv vault status` | Show vault status |
|
|
240
|
+
| `ultraenv vault diff` | Compare local env vs vault |
|
|
241
|
+
| `ultraenv vault verify` | Verify vault integrity |
|
|
242
|
+
|
|
243
|
+
### Environment Commands
|
|
244
|
+
|
|
245
|
+
| Command | Description |
|
|
246
|
+
|---|---|
|
|
247
|
+
| `ultraenv envs list` | List all environments |
|
|
248
|
+
| `ultraenv envs compare` | Compare two environments |
|
|
249
|
+
| `ultraenv envs validate` | Validate all environments |
|
|
250
|
+
| `ultraenv envs create` | Create a new environment |
|
|
251
|
+
| `ultraenv envs switch` | Switch active environment |
|
|
252
|
+
|
|
253
|
+
### CI Commands
|
|
254
|
+
|
|
255
|
+
| Command | Description |
|
|
256
|
+
|---|---|
|
|
257
|
+
| `ultraenv ci validate` | Validate environment in CI (strict mode) |
|
|
258
|
+
| `ultraenv ci check-sync` | Check `.env` โ `.env.example` sync |
|
|
259
|
+
| `ultraenv ci scan` | Scan for secrets (SARIF output) |
|
|
260
|
+
| `ultraenv ci setup` | Generate CI config files |
|
|
261
|
+
|
|
262
|
+
### Utility Commands
|
|
263
|
+
|
|
264
|
+
| Command | Description |
|
|
265
|
+
|---|---|
|
|
266
|
+
| `ultraenv install-hook` | Install git pre-commit hook |
|
|
267
|
+
| `ultraenv completion` | Generate shell completions |
|
|
268
|
+
|
|
269
|
+
### Global Flags
|
|
270
|
+
|
|
271
|
+
| Flag | Description |
|
|
272
|
+
|---|---|
|
|
273
|
+
| `--config <path>` | Path to config file |
|
|
274
|
+
| `--cwd <path>` | Working directory |
|
|
275
|
+
| `--format <fmt>` | Output format: `terminal`, `json`, `silent` |
|
|
276
|
+
| `--debug` | Enable debug output |
|
|
277
|
+
| `--no-color` | Disable color output |
|
|
278
|
+
| `-q`, `--quiet` | Suppress non-error output |
|
|
279
|
+
| `-v`, `--version` | Show version |
|
|
280
|
+
| `-h`, `--help` | Show help |
|
|
281
|
+
|
|
282
|
+
### Examples
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
# Initialize a new project
|
|
286
|
+
ultraenv init
|
|
287
|
+
|
|
288
|
+
# Validate current environment
|
|
289
|
+
ultraenv validate --format json
|
|
290
|
+
|
|
291
|
+
# Generate TypeScript types
|
|
292
|
+
ultraenv typegen --format declaration --out src/env.d.ts
|
|
293
|
+
|
|
294
|
+
# Scan for secrets (terminal output)
|
|
295
|
+
ultraenv scan --scope files
|
|
296
|
+
|
|
297
|
+
# Scan for secrets (SARIF for GitHub)
|
|
298
|
+
ultraenv scan --scope all --format sarif --output results.sarif
|
|
299
|
+
|
|
300
|
+
# Sync .env.example
|
|
301
|
+
ultraenv sync --mode generate
|
|
302
|
+
ultraenv sync --mode watch
|
|
303
|
+
|
|
304
|
+
# Vault operations
|
|
305
|
+
ultraenv vault init --env production
|
|
306
|
+
ultraenv vault encrypt --env production
|
|
307
|
+
ultraenv vault decrypt --env production
|
|
308
|
+
|
|
309
|
+
# Setup CI pipeline
|
|
310
|
+
ultraenv ci setup --platform github
|
|
311
|
+
|
|
312
|
+
# Install pre-commit hook
|
|
313
|
+
ultraenv install-hook
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## ๐ Schema Reference
|
|
319
|
+
|
|
320
|
+
The schema engine is the heart of ultraenv. It provides **30+ validators** and **10+ modifiers** for comprehensive environment variable validation with full TypeScript inference.
|
|
321
|
+
|
|
322
|
+
### The `t` Factory
|
|
323
|
+
|
|
324
|
+
All schema builders are created via the `t` factory object:
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import { defineEnv, t } from 'ultraenv';
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
### Core Validators
|
|
333
|
+
|
|
334
|
+
#### `t.string()` โ String Validation
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
// Basic string
|
|
338
|
+
API_KEY: t.string().required(),
|
|
339
|
+
|
|
340
|
+
// With format validation
|
|
341
|
+
WEBSITE_URL: t.string().format('url').required(),
|
|
342
|
+
ADMIN_EMAIL: t.string().format('email').optional(),
|
|
343
|
+
APP_ID: t.string().format('uuid').required(),
|
|
344
|
+
|
|
345
|
+
// Length constraints
|
|
346
|
+
USERNAME: t.string().minLength(3).maxLength(50).required(),
|
|
347
|
+
|
|
348
|
+
// Regex pattern
|
|
349
|
+
SLUG: t.string().pattern(/^[a-z0-9-]+$/).required(),
|
|
350
|
+
|
|
351
|
+
// Enum shorthand
|
|
352
|
+
LOG_LEVEL: t.string().enum(['debug', 'info', 'warn', 'error'] as const).default('info'),
|
|
353
|
+
|
|
354
|
+
// Auto-trim whitespace
|
|
355
|
+
TRIMMED_VALUE: t.string().trim().required(),
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
| Method | Parameter | Description |
|
|
359
|
+
|---|---|---|
|
|
360
|
+
| `.required()` | โ | Field must be set |
|
|
361
|
+
| `.optional()` | โ | Field may be undefined |
|
|
362
|
+
| `.default(value)` | `string` | Default when not set |
|
|
363
|
+
| `.description(desc)` | `string` | JSDoc description |
|
|
364
|
+
| `.minLength(n)` | `number` | Minimum string length |
|
|
365
|
+
| `.maxLength(n)` | `number` | Maximum string length |
|
|
366
|
+
| `.pattern(regex)` | `RegExp` | Must match regex |
|
|
367
|
+
| `.format(fmt)` | `string` | Predefined format shortcut |
|
|
368
|
+
| `.enum(values)` | `readonly string[]` | Allowed values |
|
|
369
|
+
| `.trim()` | `boolean?` | Trim whitespace |
|
|
370
|
+
| `.transform(fn)` | `(v) => v` | Transform after parse |
|
|
371
|
+
| `.validate(fn)` | `(v) => string \| void` | Custom validation |
|
|
372
|
+
| `.deprecated(msg)` | `string` | Deprecation warning |
|
|
373
|
+
| `.secret()` | โ | Mark as secret (masked in output) |
|
|
374
|
+
| `.alias(name)` | `string` | Alternative variable name |
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
#### `t.number()` โ Number Validation
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// Basic number (parses from string)
|
|
381
|
+
PORT: t.number().required(),
|
|
382
|
+
|
|
383
|
+
// Port validation (1-65535)
|
|
384
|
+
PORT: t.number().port().default(3000),
|
|
385
|
+
|
|
386
|
+
// Range constraints
|
|
387
|
+
MIN_AGE: t.number().min(0).max(150).required(),
|
|
388
|
+
|
|
389
|
+
// Integer constraint
|
|
390
|
+
PAGE_SIZE: t.number().integer().min(1).max(100).default(20),
|
|
391
|
+
|
|
392
|
+
// Positive / negative / non-negative
|
|
393
|
+
BALANCE: t.number().nonNegative().default(0),
|
|
394
|
+
DISCOUNT: t.number().negative().optional(),
|
|
395
|
+
|
|
396
|
+
// Finite check (no NaN or Infinity)
|
|
397
|
+
RATIO: t.number().finite().positive().required(),
|
|
398
|
+
|
|
399
|
+
// Custom parser
|
|
400
|
+
HEX_PORT: t.number().parse(v => parseInt(v, 16)).default(0x1F90),
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
| Method | Parameter | Description |
|
|
404
|
+
|---|---|---|
|
|
405
|
+
| `.min(n)` | `number` | Minimum value |
|
|
406
|
+
| `.max(n)` | `number` | Maximum value |
|
|
407
|
+
| `.integer()` | โ | Must be integer |
|
|
408
|
+
| `.positive()` | โ | Must be > 0 |
|
|
409
|
+
| `.negative()` | โ | Must be < 0 |
|
|
410
|
+
| `.nonNegative()` | โ | Must be >= 0 |
|
|
411
|
+
| `.finite()` | โ | No NaN / Infinity |
|
|
412
|
+
| `.port()` | โ | Valid port (1โ65535) |
|
|
413
|
+
| `.parse(fn)` | `(raw) => number` | Custom parser |
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
#### `t.boolean()` โ Boolean Validation
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
DEBUG: t.boolean().default(false),
|
|
421
|
+
ENABLE_CACHE: t.boolean().required(),
|
|
422
|
+
|
|
423
|
+
// Custom truthy/falsy values
|
|
424
|
+
FEATURE_FLAG: t.boolean()
|
|
425
|
+
.truthy(['on', 'enabled', '1', 'yes'])
|
|
426
|
+
.falsy(['off', 'disabled', '0', 'no'])
|
|
427
|
+
.default(false),
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Default truthy: `'true'`, `'1'`, `'yes'`, `'on'`, `'TRUE'`, `'True'`, `'YES'`, `'ON'`
|
|
431
|
+
|
|
432
|
+
Default falsy: `'false'`, `'0'`, `'no'`, `'off'`, `'FALSE'`, `'False'`, `'NO'`, `'OFF'`, `''`
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
#### `t.enum()` โ Enum / Literal Union
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
// TypeScript literal union inference
|
|
440
|
+
NODE_ENV: t.enum(['development', 'staging', 'production'] as const).required(),
|
|
441
|
+
// Type: 'development' | 'staging' | 'production'
|
|
442
|
+
|
|
443
|
+
LOG_LEVEL: t.enum(['debug', 'info', 'warn', 'error', 'fatal'] as const)
|
|
444
|
+
.caseInsensitive()
|
|
445
|
+
.default('info'),
|
|
446
|
+
|
|
447
|
+
COLOR_SCHEME: t.enum(['light', 'dark', 'system'] as const).optional(),
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
| Method | Parameter | Description |
|
|
451
|
+
|---|---|---|
|
|
452
|
+
| `.caseInsensitive()` | `boolean?` | Case-insensitive matching |
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
### Advanced Validators
|
|
457
|
+
|
|
458
|
+
#### `t.url()` โ URL Validation
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
PUBLIC_URL: t.url().required(),
|
|
462
|
+
API_ENDPOINT: t.url({ protocols: ['https'] }).required(),
|
|
463
|
+
REDIRECT_URL: t.url({ allowRelative: true }).optional(),
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Options: `protocols`, `allowRelative`, `allowQuery`, `requireTld`
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
#### `t.email()` โ Email Validation
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
ADMIN_EMAIL: t.email().required(),
|
|
474
|
+
CONTACT: t.email({ allowDisplayName: true }).optional(),
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
#### `t.ip()` โ IP Address Validation
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
SERVER_IP: t.ip().required(), // IPv4 or IPv6
|
|
483
|
+
BIND_ADDRESS: t.ipv4().required(), // IPv4 only
|
|
484
|
+
LISTEN_IPV6: t.ipv6().optional(), // IPv6 only
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
#### `t.hostname()` โ Hostname Validation
|
|
490
|
+
|
|
491
|
+
```typescript
|
|
492
|
+
SERVER_HOST: t.hostname().default('localhost'),
|
|
493
|
+
ALLOWED_HOST: t.hostname({ allowWildcard: true }).required(),
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
#### `t.port()` โ Port Validation
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
PORT: t.port().default(3000),
|
|
502
|
+
REDIS_PORT: t.port().default(6379),
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
Validates 1โ65535 range.
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
#### `t.uuid()` โ UUID Validation
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
REQUEST_ID: t.uuid().required(),
|
|
513
|
+
SESSION_ID: t.uuid({ version: 4 }).required(),
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
Supports versions: 1, 3, 4, 5, or `null` for any.
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
#### `t.array()` โ Array (Delimited String)
|
|
521
|
+
|
|
522
|
+
```typescript
|
|
523
|
+
ALLOWED_ORIGINS: t.array().required(), // comma-separated
|
|
524
|
+
TAGS: t.array().separator(';').required(), // semicolon-separated
|
|
525
|
+
FEATURES: t.array().trimItems().filterEmpty().required(), // clean items
|
|
526
|
+
ROLES: t.array().minItems(1).maxItems(10).unique().required(),
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
| Method | Parameter | Description |
|
|
530
|
+
|---|---|---|
|
|
531
|
+
| `.separator(sep)` | `string` | Split character (default: `,`) |
|
|
532
|
+
| `.trimItems()` | `boolean?` | Trim whitespace from items |
|
|
533
|
+
| `.filterEmpty()` | `boolean?` | Remove empty strings |
|
|
534
|
+
| `.minItems(n)` | `number` | Minimum items |
|
|
535
|
+
| `.maxItems(n)` | `number` | Maximum items |
|
|
536
|
+
| `.unique()` | โ | Deduplicate items |
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
#### `t.json()` โ JSON Parsing
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
FEATURE_FLAGS: t.json().required(), // unknown
|
|
544
|
+
APP_CONFIG: t.json<{ theme: string; lang: string }>().required(),
|
|
545
|
+
MIDDLEWARE_CONFIG: t.json().reviver((key, value) => value).optional(),
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
#### `t.date()` โ Date Validation
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
START_DATE: t.date().required(),
|
|
554
|
+
EXPIRY: t.date().min(new Date('2024-01-01')).optional(),
|
|
555
|
+
CREATED: t.date().format('YYYY-MM-DD').required(),
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
#### `t.bigint()` โ BigInt Validation
|
|
561
|
+
|
|
562
|
+
```typescript
|
|
563
|
+
MAX_SAFE_INTEGER: t.bigint().required(),
|
|
564
|
+
SATOSHIS: t.bigint().min(0n).parse(v => BigInt(v)).required(),
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
---
|
|
568
|
+
|
|
569
|
+
#### `t.regex()` โ Regex Validation
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
PATTERN: t.regex().required(),
|
|
573
|
+
ROUTE_MATCHER: t.regex({ flags: 'i' }).optional(),
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
578
|
+
### String Format Validators
|
|
579
|
+
|
|
580
|
+
#### `t.hex()` โ Hexadecimal String
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
COLOR: t.hex().required(),
|
|
584
|
+
API_KEY_HEX: t.hex({ minLength: 32, maxLength: 64 }).required(),
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
#### `t.base64()` โ Base64 String
|
|
590
|
+
|
|
591
|
+
```typescript
|
|
592
|
+
ENCODED_DATA: t.base64().required(),
|
|
593
|
+
CERT_B64: t.base64({ paddingRequired: true }).required(),
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
#### `t.semver()` โ Semantic Version
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
APP_VERSION: t.semver().required(),
|
|
602
|
+
MIN_VERSION: t.semver({ loose: true }).optional(),
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
#### `t.cron()` โ Cron Expression
|
|
608
|
+
|
|
609
|
+
```typescript
|
|
610
|
+
SCHEDULE: t.cron().required(),
|
|
611
|
+
BACKUP_CRON: t.cron({ allowSeconds: true }).default('0 2 * * *'),
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
#### `t.duration()` โ Duration String
|
|
617
|
+
|
|
618
|
+
```typescript
|
|
619
|
+
TIMEOUT: t.duration().default('30s'),
|
|
620
|
+
CACHE_TTL: t.duration().default('1h'),
|
|
621
|
+
LEASE_TIME: t.duration().default('24h'),
|
|
622
|
+
GRACE_PERIOD: t.duration().default('500ms'),
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
Supported units: `ms`, `s`, `m`, `h`, `d`, `w`
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
629
|
+
#### `t.bytes()` โ Bytes String
|
|
630
|
+
|
|
631
|
+
```typescript
|
|
632
|
+
MAX_UPLOAD: t.bytes().default('10MB'),
|
|
633
|
+
MEMORY_LIMIT: t.bytes().default('512MB'),
|
|
634
|
+
DISK_QUOTA: t.bytes().default('1GB'),
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
Supported units: `B`, `KB`, `MB`, `GB`, `TB`, `PB`
|
|
638
|
+
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
#### `t.color()` โ Color String
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
BRAND_COLOR: t.color().required(),
|
|
645
|
+
ACCENT: t.color({ formats: ['hex', 'rgb'] }).default('#0ea5e9'),
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
---
|
|
649
|
+
|
|
650
|
+
#### `t.locale()` โ Locale Code
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
DEFAULT_LOCALE: t.locale().default('en-US'),
|
|
654
|
+
SUPPORTED_LOCALES: t.locale().required(),
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
---
|
|
658
|
+
|
|
659
|
+
#### `t.timezone()` โ IANA Timezone
|
|
660
|
+
|
|
661
|
+
```typescript
|
|
662
|
+
TZ: t.timezone().default('UTC'),
|
|
663
|
+
USER_TIMEZONE: t.timezone().required(),
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
Validates against the full IANA timezone database.
|
|
667
|
+
|
|
668
|
+
---
|
|
669
|
+
|
|
670
|
+
#### `t.country()` โ ISO Country Code
|
|
671
|
+
|
|
672
|
+
```typescript
|
|
673
|
+
COUNTRY: t.country().required(), // ISO 3166-1 alpha-2
|
|
674
|
+
REGION: t.country({ format: 'alpha-3' }).optional(), // ISO 3166-1 alpha-3
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
---
|
|
678
|
+
|
|
679
|
+
#### `t.currency()` โ ISO Currency Code
|
|
680
|
+
|
|
681
|
+
```typescript
|
|
682
|
+
CURRENCY: t.currency().required(),
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
---
|
|
686
|
+
|
|
687
|
+
#### `t.path()` โ File System Path
|
|
688
|
+
|
|
689
|
+
```typescript
|
|
690
|
+
CONFIG_PATH: t.path().required(),
|
|
691
|
+
OUTPUT_DIR: t.path({ mustExist: false }).optional(),
|
|
692
|
+
LOG_FILE: t.path({ mustExist: false, resolve: true }).default('/var/log/app.log'),
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
### Schema Modifiers
|
|
698
|
+
|
|
699
|
+
All validators support these common modifiers:
|
|
700
|
+
|
|
701
|
+
#### `.required()` / `.optional()`
|
|
702
|
+
|
|
703
|
+
```typescript
|
|
704
|
+
REQUIRED_VAR: t.string().required(), // throws if missing
|
|
705
|
+
OPTIONAL_VAR: t.string().optional(), // value is string | undefined
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
#### `.default(value)`
|
|
709
|
+
|
|
710
|
+
```typescript
|
|
711
|
+
PORT: t.number().default(3000),
|
|
712
|
+
HOST: t.string().default('localhost'),
|
|
713
|
+
ENABLED: t.boolean().default(false),
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
#### `.description(desc)`
|
|
717
|
+
|
|
718
|
+
```typescript
|
|
719
|
+
DATABASE_URL: t.string()
|
|
720
|
+
.format('url')
|
|
721
|
+
.description('Primary PostgreSQL connection string')
|
|
722
|
+
.required(),
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
Generates JSDoc comments in type generation and descriptions in `.env.example`.
|
|
726
|
+
|
|
727
|
+
#### `.transform(fn)`
|
|
728
|
+
|
|
729
|
+
```typescript
|
|
730
|
+
PORT: t.number()
|
|
731
|
+
.transform(v => Math.floor(v))
|
|
732
|
+
.default(3000),
|
|
733
|
+
|
|
734
|
+
URL: t.string()
|
|
735
|
+
.transform(v => v.replace(/\/$/, ''))
|
|
736
|
+
.required(),
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
#### `.validate(fn)`
|
|
740
|
+
|
|
741
|
+
```typescript
|
|
742
|
+
PASSWORD: t.string()
|
|
743
|
+
.minLength(12)
|
|
744
|
+
.validate(v => {
|
|
745
|
+
if (!/[A-Z]/.test(v)) return 'Must contain an uppercase letter';
|
|
746
|
+
if (!/[0-9]/.test(v)) return 'Must contain a number';
|
|
747
|
+
return undefined; // passes validation
|
|
748
|
+
})
|
|
749
|
+
.required(),
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
#### `.deprecated(msg)`
|
|
753
|
+
|
|
754
|
+
```typescript
|
|
755
|
+
OLD_API_KEY: t.string()
|
|
756
|
+
.deprecated('Use NEW_API_KEY instead โ will be removed in v2.0')
|
|
757
|
+
.optional(),
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
#### `.secret()`
|
|
761
|
+
|
|
762
|
+
```typescript
|
|
763
|
+
DATABASE_PASSWORD: t.string().secret().required(),
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
Secret values are masked in logs, scan results, and CLI output.
|
|
767
|
+
|
|
768
|
+
#### `.alias(name)`
|
|
769
|
+
|
|
770
|
+
```typescript
|
|
771
|
+
DB_URL: t.string()
|
|
772
|
+
.alias('DATABASE_URL')
|
|
773
|
+
.format('url')
|
|
774
|
+
.required(),
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
Allows the variable to be referenced by an alternative name.
|
|
778
|
+
|
|
779
|
+
#### `.conditional(config)`
|
|
780
|
+
|
|
781
|
+
```typescript
|
|
782
|
+
STRIPE_KEY: t.string()
|
|
783
|
+
.conditional({
|
|
784
|
+
check: (env) => env.PAYMENT_PROVIDER === 'stripe',
|
|
785
|
+
then: (schema) => schema.required(),
|
|
786
|
+
otherwise: (schema) => schema.optional(),
|
|
787
|
+
}),
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
Apply different requirements based on other env values.
|
|
791
|
+
|
|
792
|
+
---
|
|
793
|
+
|
|
794
|
+
### Complete Schema Example
|
|
795
|
+
|
|
796
|
+
Here's a comprehensive real-world schema:
|
|
797
|
+
|
|
798
|
+
```typescript
|
|
799
|
+
import { defineEnv, t } from 'ultraenv';
|
|
800
|
+
|
|
801
|
+
const env = defineEnv({
|
|
802
|
+
// โโ Application โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
803
|
+
NODE_ENV: t.enum(['development', 'staging', 'production'] as const)
|
|
804
|
+
.description('Application environment')
|
|
805
|
+
.default('development'),
|
|
806
|
+
|
|
807
|
+
APP_NAME: t.string()
|
|
808
|
+
.minLength(1)
|
|
809
|
+
.maxLength(100)
|
|
810
|
+
.description('Application name')
|
|
811
|
+
.required(),
|
|
812
|
+
|
|
813
|
+
APP_URL: t.url()
|
|
814
|
+
.description('Canonical application URL')
|
|
815
|
+
.required(),
|
|
816
|
+
|
|
817
|
+
// โโ Server โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
818
|
+
PORT: t.number()
|
|
819
|
+
.port()
|
|
820
|
+
.description('Server listening port')
|
|
821
|
+
.default(3000),
|
|
822
|
+
|
|
823
|
+
HOST: t.string()
|
|
824
|
+
.hostname()
|
|
825
|
+
.description('Server bind address')
|
|
826
|
+
.default('localhost'),
|
|
827
|
+
|
|
828
|
+
// โโ Database โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
829
|
+
DATABASE_URL: t.string()
|
|
830
|
+
.format('url')
|
|
831
|
+
.description('PostgreSQL connection string')
|
|
832
|
+
.secret()
|
|
833
|
+
.required(),
|
|
834
|
+
|
|
835
|
+
DATABASE_POOL_SIZE: t.number()
|
|
836
|
+
.integer()
|
|
837
|
+
.min(1)
|
|
838
|
+
.max(100)
|
|
839
|
+
.default(10),
|
|
840
|
+
|
|
841
|
+
// โโ Authentication โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
842
|
+
JWT_SECRET: t.string()
|
|
843
|
+
.minLength(32)
|
|
844
|
+
.description('JWT signing secret')
|
|
845
|
+
.secret()
|
|
846
|
+
.required(),
|
|
847
|
+
|
|
848
|
+
JWT_EXPIRY: t.duration()
|
|
849
|
+
.description('JWT token expiry duration')
|
|
850
|
+
.default('15m'),
|
|
851
|
+
|
|
852
|
+
REFRESH_TOKEN_EXPIRY: t.duration()
|
|
853
|
+
.description('Refresh token expiry duration')
|
|
854
|
+
.default('7d'),
|
|
855
|
+
|
|
856
|
+
// โโ Redis โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
857
|
+
REDIS_URL: t.string()
|
|
858
|
+
.format('url')
|
|
859
|
+
.optional()
|
|
860
|
+
.description('Redis connection URL'),
|
|
861
|
+
|
|
862
|
+
REDIS_TTL: t.duration()
|
|
863
|
+
.default('1h')
|
|
864
|
+
.description('Default Redis key TTL'),
|
|
865
|
+
|
|
866
|
+
// โโ Email โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
867
|
+
SMTP_HOST: t.string()
|
|
868
|
+
.hostname()
|
|
869
|
+
.optional(),
|
|
870
|
+
|
|
871
|
+
SMTP_PORT: t.number()
|
|
872
|
+
.port()
|
|
873
|
+
.default(587),
|
|
874
|
+
|
|
875
|
+
SMTP_USER: t.email()
|
|
876
|
+
.optional(),
|
|
877
|
+
|
|
878
|
+
SMTP_PASSWORD: t.string()
|
|
879
|
+
.secret()
|
|
880
|
+
.optional(),
|
|
881
|
+
|
|
882
|
+
// โโ File Uploads โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
883
|
+
MAX_UPLOAD_SIZE: t.bytes()
|
|
884
|
+
.default('10MB')
|
|
885
|
+
.description('Maximum file upload size'),
|
|
886
|
+
|
|
887
|
+
UPLOAD_DIR: t.path({ mustExist: false })
|
|
888
|
+
.default('./uploads'),
|
|
889
|
+
|
|
890
|
+
// โโ External APIs โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
891
|
+
STRIPE_SECRET_KEY: t.string()
|
|
892
|
+
.secret()
|
|
893
|
+
.optional()
|
|
894
|
+
.deprecated('Use STRIPE_API_KEY instead'),
|
|
895
|
+
|
|
896
|
+
STRIPE_API_KEY: t.string()
|
|
897
|
+
.secret()
|
|
898
|
+
.optional(),
|
|
899
|
+
|
|
900
|
+
STRIPE_WEBHOOK_SECRET: t.string()
|
|
901
|
+
.secret()
|
|
902
|
+
.optional(),
|
|
903
|
+
|
|
904
|
+
// โโ Internationalization โโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
905
|
+
DEFAULT_LOCALE: t.locale()
|
|
906
|
+
.default('en-US'),
|
|
907
|
+
|
|
908
|
+
DEFAULT_TIMEZONE: t.timezone()
|
|
909
|
+
.default('UTC'),
|
|
910
|
+
|
|
911
|
+
// โโ Feature Flags โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
912
|
+
ENABLE_ANALYTICS: t.boolean()
|
|
913
|
+
.default(false),
|
|
914
|
+
|
|
915
|
+
ENABLE_RATE_LIMITING: t.boolean()
|
|
916
|
+
.default(true),
|
|
917
|
+
|
|
918
|
+
RATE_LIMIT_MAX: t.number()
|
|
919
|
+
.positive()
|
|
920
|
+
.default(100),
|
|
921
|
+
|
|
922
|
+
// โโ Logging โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
923
|
+
LOG_LEVEL: t.enum(['debug', 'info', 'warn', 'error', 'fatal'] as const)
|
|
924
|
+
.default('info'),
|
|
925
|
+
|
|
926
|
+
LOG_FORMAT: t.enum(['json', 'text', 'pretty'] as const)
|
|
927
|
+
.default('json'),
|
|
928
|
+
|
|
929
|
+
// โโ CORS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
930
|
+
ALLOWED_ORIGINS: t.array()
|
|
931
|
+
.separator(';')
|
|
932
|
+
.trimItems()
|
|
933
|
+
.default(['http://localhost:3000']),
|
|
934
|
+
|
|
935
|
+
// โโ Health & Monitoring โโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
936
|
+
HEALTH_CHECK_PATH: t.string()
|
|
937
|
+
.default('/health'),
|
|
938
|
+
|
|
939
|
+
SENTRY_DSN: t.url({ allowRelative: false })
|
|
940
|
+
.optional(),
|
|
941
|
+
|
|
942
|
+
// โโ Cron Jobs โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
943
|
+
CLEANUP_CRON: t.cron()
|
|
944
|
+
.default('0 3 * * *')
|
|
945
|
+
.description('Daily cleanup schedule (3 AM)'),
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
export default env;
|
|
949
|
+
|
|
950
|
+
// All values are fully typed:
|
|
951
|
+
// env.DATABASE_URL โ string
|
|
952
|
+
// env.PORT โ number
|
|
953
|
+
// env.NODE_ENV โ 'development' | 'staging' | 'production'
|
|
954
|
+
// env.MAX_UPLOAD_SIZE โ string
|
|
955
|
+
// env.JWT_EXPIRY โ string
|
|
956
|
+
// env.ALLOWED_ORIGINS โ readonly string[]
|
|
957
|
+
// env.STRIPE_API_KEY โ string | undefined
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
---
|
|
961
|
+
|
|
962
|
+
## ๐ Encryption & Vault
|
|
963
|
+
|
|
964
|
+
ultraenv includes a built-in encrypted vault for securely managing secrets across environments.
|
|
965
|
+
|
|
966
|
+
### Quick Setup
|
|
967
|
+
|
|
968
|
+
```bash
|
|
969
|
+
# 1. Initialize the vault
|
|
970
|
+
ultraenv vault init --env production
|
|
971
|
+
|
|
972
|
+
# 2. Encrypt your environment
|
|
973
|
+
ultraenv vault encrypt --env production
|
|
974
|
+
|
|
975
|
+
# 3. Commit the vault (safe โ it's encrypted!)
|
|
976
|
+
git add .env.vault
|
|
977
|
+
git commit -m "Add encrypted production vault"
|
|
978
|
+
|
|
979
|
+
# 4. Team members decrypt with the key
|
|
980
|
+
ultraenv vault decrypt --env production
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
### Programmatic API
|
|
984
|
+
|
|
985
|
+
```typescript
|
|
986
|
+
import {
|
|
987
|
+
encryptValue,
|
|
988
|
+
decryptValue,
|
|
989
|
+
isEncryptedValue,
|
|
990
|
+
generateMasterKey,
|
|
991
|
+
formatKey,
|
|
992
|
+
} from 'ultraenv';
|
|
993
|
+
|
|
994
|
+
// Generate a new master key
|
|
995
|
+
const key = generateMasterKey(); // Buffer (32 bytes)
|
|
996
|
+
const keyFormatted = formatKey(key); // "ultraenv_key_v1_..."
|
|
997
|
+
|
|
998
|
+
// Encrypt a single value
|
|
999
|
+
const encrypted = await encryptValue(
|
|
1000
|
+
'my-super-secret-password',
|
|
1001
|
+
key,
|
|
1002
|
+
);
|
|
1003
|
+
// โ "encrypted:v1:aes-256-gcm:...base64..."
|
|
1004
|
+
|
|
1005
|
+
// Check if a value is encrypted
|
|
1006
|
+
isEncryptedValue(encrypted); // true
|
|
1007
|
+
|
|
1008
|
+
// Decrypt
|
|
1009
|
+
const decrypted = await decryptValue(encrypted, key);
|
|
1010
|
+
// โ "my-super-secret-password"
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
### Vault Workflow
|
|
1014
|
+
|
|
1015
|
+
```bash
|
|
1016
|
+
# Full workflow
|
|
1017
|
+
ultraenv vault init --env development
|
|
1018
|
+
ultraenv vault init --env staging
|
|
1019
|
+
ultraenv vault init --env production
|
|
1020
|
+
|
|
1021
|
+
ultraenv vault encrypt --env production
|
|
1022
|
+
|
|
1023
|
+
# Check status
|
|
1024
|
+
ultraenv vault status
|
|
1025
|
+
|
|
1026
|
+
# Compare local vs vault
|
|
1027
|
+
ultraenv vault diff
|
|
1028
|
+
|
|
1029
|
+
# Verify integrity
|
|
1030
|
+
ultraenv vault verify
|
|
1031
|
+
|
|
1032
|
+
# Rotate keys (re-encrypt with new key)
|
|
1033
|
+
ultraenv vault rekey --env production
|
|
1034
|
+
```
|
|
1035
|
+
|
|
1036
|
+
### Vault Files
|
|
1037
|
+
|
|
1038
|
+
| File | Description | Git |
|
|
1039
|
+
|---|---|---|
|
|
1040
|
+
| `.env.vault` | Encrypted secrets (safe to commit) | โ
Commit |
|
|
1041
|
+
| `.env.keys` | Decryption keys (NEVER commit) | โ Gitignore |
|
|
1042
|
+
|
|
1043
|
+
### Encryption Details
|
|
1044
|
+
|
|
1045
|
+
- **Algorithm**: AES-256-GCM (authenticated encryption)
|
|
1046
|
+
- **Key derivation**: HKDF with random salt
|
|
1047
|
+
- **IV**: 12-byte random nonce per value
|
|
1048
|
+
- **Auth tag**: 16-byte GCM authentication tag
|
|
1049
|
+
- **Key format**: `ultraenv_key_v1_<base64>` (32-byte / 256-bit key)
|
|
1050
|
+
|
|
1051
|
+
> ๐ See [docs/VAULT_GUIDE.md](docs/VAULT_GUIDE.md) for the complete vault documentation.
|
|
1052
|
+
|
|
1053
|
+
---
|
|
1054
|
+
|
|
1055
|
+
## ๐ Secret Scanning
|
|
1056
|
+
|
|
1057
|
+
ultraenv includes a powerful secret scanner that detects **55+ patterns** for leaked credentials, API keys, tokens, and secrets in your codebase.
|
|
1058
|
+
|
|
1059
|
+
### Quick Scan
|
|
1060
|
+
|
|
1061
|
+
```bash
|
|
1062
|
+
# Scan current directory
|
|
1063
|
+
ultraenv scan
|
|
1064
|
+
|
|
1065
|
+
# Scan specific paths
|
|
1066
|
+
ultraenv scan src/ config/
|
|
1067
|
+
|
|
1068
|
+
# Scan git history (thorough)
|
|
1069
|
+
ultraenv scan --scope git-history
|
|
1070
|
+
|
|
1071
|
+
# Scan staged files (pre-commit)
|
|
1072
|
+
ultraenv scan --scope staged
|
|
1073
|
+
|
|
1074
|
+
# Scan diff against main
|
|
1075
|
+
ultraenv scan --scope diff --from main
|
|
1076
|
+
|
|
1077
|
+
# Scan everything
|
|
1078
|
+
ultraenv scan --scope all
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
### Output Formats
|
|
1082
|
+
|
|
1083
|
+
```bash
|
|
1084
|
+
# Terminal (default โ human-readable)
|
|
1085
|
+
ultraenv scan --format terminal
|
|
1086
|
+
|
|
1087
|
+
# JSON (for integrations)
|
|
1088
|
+
ultraenv scan --format json
|
|
1089
|
+
|
|
1090
|
+
# SARIF (for GitHub Code Scanning)
|
|
1091
|
+
ultraenv scan --format sarif --output results.sarif
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
### Detected Secret Categories
|
|
1095
|
+
|
|
1096
|
+
| Category | Patterns | Examples |
|
|
1097
|
+
|---|---|---|
|
|
1098
|
+
| **AWS** | 4 | Access Key ID, Secret Key, Session Token, Account ID |
|
|
1099
|
+
| **GitHub** | 6 | PAT, OAuth Token, App Token, Refresh Token, Webhook Secret |
|
|
1100
|
+
| **Google** | 5 | API Key, OAuth Client ID/Secret, Firebase Key, Service Account |
|
|
1101
|
+
| **Stripe** | 3 | Secret Key, Publishable Key, Restricted Key |
|
|
1102
|
+
| **Slack** | 4 | Bot Token, User Token, App Token, Webhook URL |
|
|
1103
|
+
| **Private Keys** | 8 | RSA, EC, DSA, OpenSSH, PGP, PKCS#8, Encrypted, Certificate |
|
|
1104
|
+
| **Database** | 5 | MongoDB, PostgreSQL, MySQL, Redis, CouchDB URLs |
|
|
1105
|
+
| **Cloud** | 4 | Azure, DigitalOcean, Heroku, Cloudflare |
|
|
1106
|
+
| **Auth** | 4 | JWT, Base64 Creds, Auth0, Generic Secret |
|
|
1107
|
+
| **Messaging** | 2 | Telegram Bot, Discord Bot |
|
|
1108
|
+
| **Email** | 2 | SendGrid, Mailgun |
|
|
1109
|
+
| **DevOps** | 2 | PagerDuty, Datadog |
|
|
1110
|
+
| **Generic** | 5+ | API Key, Token, Password, High-Entropy Strings |
|
|
1111
|
+
| **.env** | 1 | Secret-like variable patterns |
|
|
1112
|
+
|
|
1113
|
+
### Programmatic API
|
|
1114
|
+
|
|
1115
|
+
```typescript
|
|
1116
|
+
import { scan, formatScanResult, addCustomPattern } from 'ultraenv';
|
|
1117
|
+
|
|
1118
|
+
// Add a custom pattern
|
|
1119
|
+
addCustomPattern({
|
|
1120
|
+
id: 'my-company-api-key',
|
|
1121
|
+
name: 'My Company API Key',
|
|
1122
|
+
pattern: /MCKEY_[A-Za-z0-9]{32}/g,
|
|
1123
|
+
confidence: 0.9,
|
|
1124
|
+
severity: 'critical',
|
|
1125
|
+
description: 'Company-specific API key',
|
|
1126
|
+
remediation: 'Rotate the key and store in a vault.',
|
|
1127
|
+
category: 'internal',
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
// Run a scan
|
|
1131
|
+
const result = await scan({
|
|
1132
|
+
cwd: '/path/to/project',
|
|
1133
|
+
include: ['src/', 'config/'],
|
|
1134
|
+
exclude: ['**/*.test.ts', '**/node_modules/**'],
|
|
1135
|
+
scanGitHistory: false,
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
if (result.found) {
|
|
1139
|
+
console.log(`Found ${result.secrets.length} potential secrets!`);
|
|
1140
|
+
for (const secret of result.secrets) {
|
|
1141
|
+
console.log(` ${secret.type} in ${secret.file}:${secret.line}`);
|
|
1142
|
+
console.log(` Confidence: ${(secret.confidence * 100).toFixed(0)}%`);
|
|
1143
|
+
console.log(` Remediation: ${secret.pattern.remediation}`);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
### Git Hook Integration
|
|
1149
|
+
|
|
1150
|
+
```bash
|
|
1151
|
+
# Install pre-commit hook
|
|
1152
|
+
ultraenv install-hook
|
|
1153
|
+
|
|
1154
|
+
# This runs `ultraenv scan --scope staged` before every commit
|
|
1155
|
+
# Commits with detected secrets will be blocked
|
|
1156
|
+
```
|
|
1157
|
+
|
|
1158
|
+
> ๐ See [docs/SECRET_SCANNING.md](docs/SECRET_SCANNING.md) for the complete scanning documentation.
|
|
1159
|
+
|
|
1160
|
+
---
|
|
1161
|
+
|
|
1162
|
+
## ๐ TypeScript Type Generation
|
|
1163
|
+
|
|
1164
|
+
ultraenv can automatically generate TypeScript type definitions from your schema.
|
|
1165
|
+
|
|
1166
|
+
### Declaration File (`.d.ts`)
|
|
1167
|
+
|
|
1168
|
+
```bash
|
|
1169
|
+
ultraenv typegen --format declaration --out src/env.d.ts
|
|
1170
|
+
```
|
|
1171
|
+
|
|
1172
|
+
Generates:
|
|
1173
|
+
|
|
1174
|
+
```typescript
|
|
1175
|
+
// Auto-generated by ultraenv โ DO NOT EDIT
|
|
1176
|
+
declare namespace NodeJS {
|
|
1177
|
+
interface ProcessEnv {
|
|
1178
|
+
/** Application environment */
|
|
1179
|
+
NODE_ENV: 'development' | 'staging' | 'production';
|
|
1180
|
+
|
|
1181
|
+
/** Primary PostgreSQL connection string */
|
|
1182
|
+
DATABASE_URL: string;
|
|
1183
|
+
|
|
1184
|
+
/** Server listening port */
|
|
1185
|
+
PORT: number;
|
|
1186
|
+
|
|
1187
|
+
/** Application URL */
|
|
1188
|
+
APP_URL: string;
|
|
1189
|
+
|
|
1190
|
+
/** JWT signing secret */
|
|
1191
|
+
JWT_SECRET: string;
|
|
1192
|
+
|
|
1193
|
+
/** Optional admin email */
|
|
1194
|
+
ADMIN_EMAIL?: string;
|
|
1195
|
+
|
|
1196
|
+
// ... all your env vars with types and JSDoc
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
```
|
|
1200
|
+
|
|
1201
|
+
### TypeScript Module
|
|
1202
|
+
|
|
1203
|
+
```bash
|
|
1204
|
+
ultraenv typegen --format module --out src/env.ts
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
Generates a typed module with runtime values:
|
|
1208
|
+
|
|
1209
|
+
```typescript
|
|
1210
|
+
// Auto-generated by ultraenv โ DO NOT EDIT
|
|
1211
|
+
export interface Env {
|
|
1212
|
+
/** Application environment */
|
|
1213
|
+
readonly NODE_ENV: 'development' | 'staging' | 'production';
|
|
1214
|
+
/** Primary PostgreSQL connection string */
|
|
1215
|
+
readonly DATABASE_URL: string;
|
|
1216
|
+
// ...
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
export const env: Env = {
|
|
1220
|
+
NODE_ENV: process.env.NODE_ENV as Env['NODE_ENV'],
|
|
1221
|
+
DATABASE_URL: process.env.DATABASE_URL as string,
|
|
1222
|
+
// ...
|
|
1223
|
+
};
|
|
1224
|
+
|
|
1225
|
+
export default env;
|
|
1226
|
+
```
|
|
1227
|
+
|
|
1228
|
+
### JSON Schema
|
|
1229
|
+
|
|
1230
|
+
```bash
|
|
1231
|
+
ultraenv typegen --format json-schema --out env.schema.json
|
|
1232
|
+
```
|
|
1233
|
+
|
|
1234
|
+
Generates a standard JSON Schema for tool integrations.
|
|
1235
|
+
|
|
1236
|
+
### Watch Mode
|
|
1237
|
+
|
|
1238
|
+
```bash
|
|
1239
|
+
ultraenv typegen --format declaration --out src/env.d.ts --watch
|
|
1240
|
+
```
|
|
1241
|
+
|
|
1242
|
+
Automatically regenerates types when your `.env` or config changes.
|
|
1243
|
+
|
|
1244
|
+
### Programmatic API
|
|
1245
|
+
|
|
1246
|
+
```typescript
|
|
1247
|
+
import {
|
|
1248
|
+
generateDeclaration,
|
|
1249
|
+
generateModule,
|
|
1250
|
+
generateJsonSchema,
|
|
1251
|
+
} from 'ultraenv';
|
|
1252
|
+
|
|
1253
|
+
// Generate declaration content
|
|
1254
|
+
const dts = generateDeclaration(envVars, schema, {
|
|
1255
|
+
interfaceName: 'Env',
|
|
1256
|
+
jsdoc: true,
|
|
1257
|
+
indent: 4,
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
// Generate module content
|
|
1261
|
+
const moduleContent = generateModule(schema, {
|
|
1262
|
+
interfaceName: 'Env',
|
|
1263
|
+
jsdoc: true,
|
|
1264
|
+
indent: 2,
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
// Generate JSON Schema
|
|
1268
|
+
const jsonSchema = generateJsonSchema(schema, {
|
|
1269
|
+
includeDescriptions: true,
|
|
1270
|
+
indent: 2,
|
|
1271
|
+
});
|
|
1272
|
+
```
|
|
1273
|
+
|
|
1274
|
+
> ๐ See [docs/TYPE_GENERATION.md](docs/TYPE_GENERATION.md) for the complete type generation documentation.
|
|
1275
|
+
|
|
1276
|
+
---
|
|
1277
|
+
|
|
1278
|
+
## ๐ .env.example Sync
|
|
1279
|
+
|
|
1280
|
+
Never let your `.env.example` drift out of sync with your `.env` file again.
|
|
1281
|
+
|
|
1282
|
+
### Check Sync
|
|
1283
|
+
|
|
1284
|
+
```bash
|
|
1285
|
+
ultraenv sync --mode check
|
|
1286
|
+
```
|
|
1287
|
+
|
|
1288
|
+
Output:
|
|
1289
|
+
|
|
1290
|
+
```
|
|
1291
|
+
๐ .env Sync Check
|
|
1292
|
+
|
|
1293
|
+
โ
12 variables match
|
|
1294
|
+
โ ๏ธ 2 variables missing from .env.example:
|
|
1295
|
+
- STRIPE_API_KEY
|
|
1296
|
+
- REDIS_URL
|
|
1297
|
+
โน๏ธ 1 variable in .env.example but not in .env:
|
|
1298
|
+
- OLD_FEATURE_FLAG
|
|
1299
|
+
|
|
1300
|
+
ACTION NEEDED
|
|
1301
|
+
Your .env is out of sync with .env.example.
|
|
1302
|
+
Run "ultraenv sync --mode generate" to update .env.example
|
|
1303
|
+
```
|
|
1304
|
+
|
|
1305
|
+
### Generate
|
|
1306
|
+
|
|
1307
|
+
```bash
|
|
1308
|
+
ultraenv sync --mode generate
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
Auto-generates `.env.example` with types, defaults, and descriptions.
|
|
1312
|
+
|
|
1313
|
+
### Interactive Mode
|
|
1314
|
+
|
|
1315
|
+
```bash
|
|
1316
|
+
ultraenv sync --mode interactive
|
|
1317
|
+
```
|
|
1318
|
+
|
|
1319
|
+
Shows differences and lets you review before updating.
|
|
1320
|
+
|
|
1321
|
+
### Watch Mode
|
|
1322
|
+
|
|
1323
|
+
```bash
|
|
1324
|
+
ultraenv sync --mode watch
|
|
1325
|
+
```
|
|
1326
|
+
|
|
1327
|
+
Watches `.env` for changes and auto-updates `.env.example`.
|
|
1328
|
+
|
|
1329
|
+
---
|
|
1330
|
+
|
|
1331
|
+
## ๐ Multi-Environment Management
|
|
1332
|
+
|
|
1333
|
+
ultraenv supports **11 `.env` file variants** with proper priority cascading.
|
|
1334
|
+
|
|
1335
|
+
### Supported Files (Lowest โ Highest Priority)
|
|
1336
|
+
|
|
1337
|
+
| File | Use Case |
|
|
1338
|
+
|---|---|
|
|
1339
|
+
| `.env` | Default, shared across all environments |
|
|
1340
|
+
| `.env.local` | Local overrides (gitignored) |
|
|
1341
|
+
| `.env.development` | Development-specific |
|
|
1342
|
+
| `.env.development.local` | Local dev overrides (gitignored) |
|
|
1343
|
+
| `.env.test` | Test-specific |
|
|
1344
|
+
| `.env.test.local` | Local test overrides (gitignored) |
|
|
1345
|
+
| `.env.production` | Production-specific |
|
|
1346
|
+
| `.env.production.local` | Local prod overrides (gitignored) |
|
|
1347
|
+
| `.env.staging` | Staging-specific |
|
|
1348
|
+
| `.env.staging.local` | Local staging overrides (gitignored) |
|
|
1349
|
+
| `.env.ci` | CI/CD-specific |
|
|
1350
|
+
|
|
1351
|
+
### Cascade Order
|
|
1352
|
+
|
|
1353
|
+
Variables are merged in priority order โ **higher-priority files override lower**:
|
|
1354
|
+
|
|
1355
|
+
```
|
|
1356
|
+
.env โ .env.local โ .env.development โ .env.development.local โ .env.production โ ...
|
|
1357
|
+
```
|
|
1358
|
+
|
|
1359
|
+
### CLI Commands
|
|
1360
|
+
|
|
1361
|
+
```bash
|
|
1362
|
+
# List all environments
|
|
1363
|
+
ultraenv envs list
|
|
1364
|
+
|
|
1365
|
+
# Compare two environments
|
|
1366
|
+
ultraenv envs compare development production
|
|
1367
|
+
|
|
1368
|
+
# Validate all environments
|
|
1369
|
+
ultraenv envs validate
|
|
1370
|
+
|
|
1371
|
+
# Create a new environment
|
|
1372
|
+
ultraenv envs create staging
|
|
1373
|
+
|
|
1374
|
+
# Switch active environment
|
|
1375
|
+
ultraenv envs switch production
|
|
1376
|
+
```
|
|
1377
|
+
|
|
1378
|
+
---
|
|
1379
|
+
|
|
1380
|
+
## ๐งฉ Framework Presets
|
|
1381
|
+
|
|
1382
|
+
ultraenv ships with **9 built-in framework presets** that provide ready-made schemas, file loading order, and framework-specific validation.
|
|
1383
|
+
|
|
1384
|
+
### Available Presets
|
|
1385
|
+
|
|
1386
|
+
| Preset | ID | Tags |
|
|
1387
|
+
|---|---|---|
|
|
1388
|
+
| **Next.js** | `nextjs` | `framework`, `react`, `ssr`, `fullstack` |
|
|
1389
|
+
| **Vite** | `vite` | `framework`, `build-tool` |
|
|
1390
|
+
| **Nuxt** | `nuxt` | `framework`, `vue`, `ssr` |
|
|
1391
|
+
| **Remix** | `remix` | `framework`, `react`, `ssr` |
|
|
1392
|
+
| **SvelteKit** | `sveltekit` | `framework`, `svelte`, `ssr` |
|
|
1393
|
+
| **Express** | `express` | `backend`, `server` |
|
|
1394
|
+
| **Fastify** | `fastify` | `backend`, `server` |
|
|
1395
|
+
| **Docker** | `docker` | `container`, `devops` |
|
|
1396
|
+
| **AWS Lambda** | `aws-lambda` | `serverless`, `cloud` |
|
|
1397
|
+
|
|
1398
|
+
### Using Presets
|
|
1399
|
+
|
|
1400
|
+
```typescript
|
|
1401
|
+
import { defineEnv, t, getPreset } from 'ultraenv';
|
|
1402
|
+
|
|
1403
|
+
// Get the Next.js preset schema
|
|
1404
|
+
const nextjsPreset = getPreset('nextjs');
|
|
1405
|
+
|
|
1406
|
+
// Use it directly
|
|
1407
|
+
const env = defineEnv(nextjsPreset.schema);
|
|
1408
|
+
|
|
1409
|
+
// Or extend it with your own variables
|
|
1410
|
+
const env = defineEnv({
|
|
1411
|
+
...nextjsPreset.schema,
|
|
1412
|
+
|
|
1413
|
+
// Your custom variables
|
|
1414
|
+
CUSTOM_FEATURE: t.boolean().default(false),
|
|
1415
|
+
ANALYTICS_KEY: t.string().secret().optional(),
|
|
1416
|
+
});
|
|
1417
|
+
```
|
|
1418
|
+
|
|
1419
|
+
### Next.js Preset Features
|
|
1420
|
+
|
|
1421
|
+
The Next.js preset includes:
|
|
1422
|
+
|
|
1423
|
+
- **Client/server variable separation** โ warns if secrets are in `NEXT_PUBLIC_*` vars
|
|
1424
|
+
- **Common Next.js variables** โ `DATABASE_URL`, `NEXTAUTH_SECRET`, `NEXT_TELEMETRY_DISABLED`, etc.
|
|
1425
|
+
- **Image optimization** โ `NEXT_PUBLIC_IMAGE_DOMAINS`, `NEXT_PUBLIC_IMAGE_REMOTE_PATTERNS`
|
|
1426
|
+
- **Proper file loading order** โ `.env`, `.env.local`, `.env.development`, `.env.production`
|
|
1427
|
+
|
|
1428
|
+
### Client Leak Detection
|
|
1429
|
+
|
|
1430
|
+
```typescript
|
|
1431
|
+
import { detectClientLeakCandidates } from 'ultraenv';
|
|
1432
|
+
|
|
1433
|
+
const warnings = detectClientLeakCandidates(process.env);
|
|
1434
|
+
// โ ['NEXT_PUBLIC_SECRET_KEY: appears to be a secret exposed to the client bundle']
|
|
1435
|
+
```
|
|
1436
|
+
|
|
1437
|
+
> ๐ See [docs/FRAMEWORK_PRESETS.md](docs/FRAMEWORK_PRESETS.md) for complete preset documentation.
|
|
1438
|
+
|
|
1439
|
+
---
|
|
1440
|
+
|
|
1441
|
+
## ๐ CI/CD Integration
|
|
1442
|
+
|
|
1443
|
+
### GitHub Actions Setup
|
|
1444
|
+
|
|
1445
|
+
```bash
|
|
1446
|
+
ultraenv ci setup --platform github
|
|
1447
|
+
```
|
|
1448
|
+
|
|
1449
|
+
Generates `.github/workflows/ultraenv.yml`:
|
|
1450
|
+
|
|
1451
|
+
```yaml
|
|
1452
|
+
name: Ultraenv CI
|
|
1453
|
+
|
|
1454
|
+
on:
|
|
1455
|
+
push:
|
|
1456
|
+
branches: [main]
|
|
1457
|
+
pull_request:
|
|
1458
|
+
branches: [main]
|
|
1459
|
+
|
|
1460
|
+
jobs:
|
|
1461
|
+
validate:
|
|
1462
|
+
runs-on: ubuntu-latest
|
|
1463
|
+
steps:
|
|
1464
|
+
- uses: actions/checkout@v4
|
|
1465
|
+
|
|
1466
|
+
- name: Setup Node.js
|
|
1467
|
+
uses: actions/setup-node@v4
|
|
1468
|
+
with:
|
|
1469
|
+
node-version: '20'
|
|
1470
|
+
|
|
1471
|
+
- name: Install ultraenv
|
|
1472
|
+
run: npm install -g ultraenv
|
|
1473
|
+
|
|
1474
|
+
- name: Validate environment
|
|
1475
|
+
run: ultraenv ci validate --strict
|
|
1476
|
+
|
|
1477
|
+
- name: Check .env sync
|
|
1478
|
+
run: ultraenv ci check-sync
|
|
1479
|
+
|
|
1480
|
+
- name: Scan for secrets
|
|
1481
|
+
run: ultraenv ci scan --format sarif --output results.sarif
|
|
1482
|
+
continue-on-error: true
|
|
1483
|
+
|
|
1484
|
+
- name: Upload SARIF results
|
|
1485
|
+
if: always()
|
|
1486
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
1487
|
+
with:
|
|
1488
|
+
sarif_file: results.sarif
|
|
1489
|
+
continue-on-error: true
|
|
1490
|
+
```
|
|
1491
|
+
|
|
1492
|
+
### GitLab CI Setup
|
|
1493
|
+
|
|
1494
|
+
```bash
|
|
1495
|
+
ultraenv ci setup --platform gitlab
|
|
1496
|
+
```
|
|
1497
|
+
|
|
1498
|
+
### CI Commands
|
|
1499
|
+
|
|
1500
|
+
```bash
|
|
1501
|
+
# Validate in strict mode (fails on warnings)
|
|
1502
|
+
ultraenv ci validate --strict
|
|
1503
|
+
|
|
1504
|
+
# Check .env โ .env.example sync
|
|
1505
|
+
ultraenv ci check-sync
|
|
1506
|
+
|
|
1507
|
+
# Scan for secrets with SARIF output
|
|
1508
|
+
ultraenv ci scan --format sarif --output results.sarif
|
|
1509
|
+
```
|
|
1510
|
+
|
|
1511
|
+
### GitHub Code Scanning Integration
|
|
1512
|
+
|
|
1513
|
+
The SARIF output integrates directly with [GitHub Code Scanning](https://docs.github.com/en/code-security/code-scanning):
|
|
1514
|
+
|
|
1515
|
+
```yaml
|
|
1516
|
+
- name: Scan for secrets
|
|
1517
|
+
run: ultraenv ci scan --format sarif --output results.sarif
|
|
1518
|
+
|
|
1519
|
+
- name: Upload SARIF results
|
|
1520
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
1521
|
+
with:
|
|
1522
|
+
sarif_file: results.sarif
|
|
1523
|
+
```
|
|
1524
|
+
|
|
1525
|
+
> ๐ See [docs/CI_CD_INTEGRATION.md](docs/CI_CD_INTEGRATION.md) for the complete CI/CD documentation.
|
|
1526
|
+
|
|
1527
|
+
---
|
|
1528
|
+
|
|
1529
|
+
## ๐ Programmatic API Reference
|
|
1530
|
+
|
|
1531
|
+
### Core Loading
|
|
1532
|
+
|
|
1533
|
+
```typescript
|
|
1534
|
+
import { load, loadSync, loadWithResult } from 'ultraenv';
|
|
1535
|
+
|
|
1536
|
+
// Simple load (dotenv-compatible)
|
|
1537
|
+
load();
|
|
1538
|
+
// Reads .env, sets process.env
|
|
1539
|
+
|
|
1540
|
+
// With options
|
|
1541
|
+
load({
|
|
1542
|
+
envDir: './config',
|
|
1543
|
+
expandVariables: true,
|
|
1544
|
+
overrideProcessEnv: true,
|
|
1545
|
+
mergeStrategy: 'last-wins',
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1548
|
+
// Full result
|
|
1549
|
+
const result = loadWithResult();
|
|
1550
|
+
result.env // Record<string, string>
|
|
1551
|
+
result.metadata // { totalVars, filesParsed, loadTimeMs, ... }
|
|
1552
|
+
result.parsed // ParsedEnvFile[]
|
|
1553
|
+
```
|
|
1554
|
+
|
|
1555
|
+
### Schema Validation
|
|
1556
|
+
|
|
1557
|
+
```typescript
|
|
1558
|
+
import { defineEnv, tryDefineEnv, validate, t } from 'ultraenv';
|
|
1559
|
+
|
|
1560
|
+
// Strict: throws on validation failure
|
|
1561
|
+
const env = defineEnv({
|
|
1562
|
+
PORT: t.number().port().default(3000),
|
|
1563
|
+
NODE_ENV: t.enum(['development', 'production'] as const).required(),
|
|
1564
|
+
});
|
|
1565
|
+
|
|
1566
|
+
// Non-throwing variant
|
|
1567
|
+
const result = tryDefineEnv({
|
|
1568
|
+
PORT: t.number().port().default(3000),
|
|
1569
|
+
NODE_ENV: t.enum(['development', 'production'] as const).required(),
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
if (result.valid) {
|
|
1573
|
+
console.log(result.values); // typed values
|
|
1574
|
+
} else {
|
|
1575
|
+
console.log(result.errors); // validation errors
|
|
1576
|
+
console.log(result.unknown); // unknown variables
|
|
1577
|
+
}
|
|
1578
|
+
```
|
|
1579
|
+
|
|
1580
|
+
### Watching for Changes
|
|
1581
|
+
|
|
1582
|
+
```typescript
|
|
1583
|
+
import { createWatcher } from 'ultraenv';
|
|
1584
|
+
|
|
1585
|
+
const watcher = createWatcher({
|
|
1586
|
+
files: ['.env', '.env.local'],
|
|
1587
|
+
recursive: false,
|
|
1588
|
+
debounceMs: 100,
|
|
1589
|
+
initial: true,
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1592
|
+
watcher.on('change', (event) => {
|
|
1593
|
+
console.log(`File ${event.path} was ${event.type}`);
|
|
1594
|
+
// Reload your environment here
|
|
1595
|
+
});
|
|
1596
|
+
|
|
1597
|
+
watcher.start();
|
|
1598
|
+
|
|
1599
|
+
// Later...
|
|
1600
|
+
watcher.stop();
|
|
1601
|
+
```
|
|
1602
|
+
|
|
1603
|
+
### Health Checks
|
|
1604
|
+
|
|
1605
|
+
```typescript
|
|
1606
|
+
import {
|
|
1607
|
+
healthCheck,
|
|
1608
|
+
liveCheck,
|
|
1609
|
+
readinessCheck,
|
|
1610
|
+
} from 'ultraenv';
|
|
1611
|
+
|
|
1612
|
+
// Full health check (safe to expose via HTTP)
|
|
1613
|
+
const health = healthCheck();
|
|
1614
|
+
// โ { status: 'ok', loaded: 42, valid: 42, environment: 'production', ... }
|
|
1615
|
+
|
|
1616
|
+
// Liveness probe (minimal)
|
|
1617
|
+
const live = liveCheck();
|
|
1618
|
+
// โ { status: 'ok', timestamp: '...' }
|
|
1619
|
+
|
|
1620
|
+
// Readiness probe (checks specific vars)
|
|
1621
|
+
const ready = readinessCheck(['DATABASE_URL', 'REDIS_URL']);
|
|
1622
|
+
// โ { status: 'ok', ready: true, missing: [], timestamp: '...' }
|
|
1623
|
+
```
|
|
1624
|
+
|
|
1625
|
+
### Express Middleware
|
|
1626
|
+
|
|
1627
|
+
```typescript
|
|
1628
|
+
import express from 'express';
|
|
1629
|
+
import { ultraenvMiddleware, healthCheckRoute } from 'ultraenv';
|
|
1630
|
+
|
|
1631
|
+
const app = express();
|
|
1632
|
+
|
|
1633
|
+
// Load and validate env
|
|
1634
|
+
app.use(ultraenvMiddleware({
|
|
1635
|
+
schema: {
|
|
1636
|
+
PORT: { type: 'number', port: true, default: 3000 },
|
|
1637
|
+
},
|
|
1638
|
+
}));
|
|
1639
|
+
|
|
1640
|
+
// Health check endpoint
|
|
1641
|
+
app.get('/health', healthCheckRoute());
|
|
1642
|
+
|
|
1643
|
+
// readiness endpoint
|
|
1644
|
+
app.get('/ready', readinessCheck(['DATABASE_URL']));
|
|
1645
|
+
```
|
|
1646
|
+
|
|
1647
|
+
### Fastify Plugin
|
|
1648
|
+
|
|
1649
|
+
```typescript
|
|
1650
|
+
import Fastify from 'fastify';
|
|
1651
|
+
import { createUltraenvPlugin } from 'ultraenv/fastify';
|
|
1652
|
+
|
|
1653
|
+
const app = Fastify();
|
|
1654
|
+
|
|
1655
|
+
app.register(createUltraenvPlugin({
|
|
1656
|
+
schema: {
|
|
1657
|
+
DATABASE_URL: { type: 'string', format: 'url' },
|
|
1658
|
+
},
|
|
1659
|
+
}));
|
|
1660
|
+
```
|
|
1661
|
+
|
|
1662
|
+
### Error Handling
|
|
1663
|
+
|
|
1664
|
+
```typescript
|
|
1665
|
+
import {
|
|
1666
|
+
UltraenvError,
|
|
1667
|
+
ValidationError,
|
|
1668
|
+
ParseError,
|
|
1669
|
+
EncryptionError,
|
|
1670
|
+
VaultError,
|
|
1671
|
+
ScanError,
|
|
1672
|
+
ConfigError,
|
|
1673
|
+
isUltraenvError,
|
|
1674
|
+
} from 'ultraenv';
|
|
1675
|
+
|
|
1676
|
+
try {
|
|
1677
|
+
// ... ultraenv operations
|
|
1678
|
+
} catch (error) {
|
|
1679
|
+
if (isUltraenvError(error)) {
|
|
1680
|
+
console.log(error.code); // 'VALIDATION_ERROR'
|
|
1681
|
+
console.log(error.message); // Human-readable message
|
|
1682
|
+
console.log(error.hint); // Actionable fix suggestion
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
```
|
|
1686
|
+
|
|
1687
|
+
### Vault API
|
|
1688
|
+
|
|
1689
|
+
```typescript
|
|
1690
|
+
import {
|
|
1691
|
+
encryptEnvironment,
|
|
1692
|
+
decryptEnvironment,
|
|
1693
|
+
encryptValue,
|
|
1694
|
+
decryptValue,
|
|
1695
|
+
isEncryptedValue,
|
|
1696
|
+
generateMasterKey,
|
|
1697
|
+
deriveEnvironmentKey,
|
|
1698
|
+
formatKey,
|
|
1699
|
+
parseKey,
|
|
1700
|
+
generateKeysFile,
|
|
1701
|
+
parseKeysFile,
|
|
1702
|
+
rotateKey,
|
|
1703
|
+
} from 'ultraenv';
|
|
1704
|
+
```
|
|
1705
|
+
|
|
1706
|
+
### Scanner API
|
|
1707
|
+
|
|
1708
|
+
```typescript
|
|
1709
|
+
import {
|
|
1710
|
+
scan,
|
|
1711
|
+
scanFiles,
|
|
1712
|
+
scanGitHistory,
|
|
1713
|
+
scanStagedFiles,
|
|
1714
|
+
scanDiff,
|
|
1715
|
+
matchPatterns,
|
|
1716
|
+
addCustomPattern,
|
|
1717
|
+
removeCustomPattern,
|
|
1718
|
+
resetPatterns,
|
|
1719
|
+
detectHighEntropyStrings,
|
|
1720
|
+
formatScanResult,
|
|
1721
|
+
} from 'ultraenv';
|
|
1722
|
+
```
|
|
1723
|
+
|
|
1724
|
+
### Sync API
|
|
1725
|
+
|
|
1726
|
+
```typescript
|
|
1727
|
+
import {
|
|
1728
|
+
generateExampleFile,
|
|
1729
|
+
generateExampleContent,
|
|
1730
|
+
needsUpdate,
|
|
1731
|
+
compareSync,
|
|
1732
|
+
compareValues,
|
|
1733
|
+
createSyncWatcher,
|
|
1734
|
+
} from 'ultraenv';
|
|
1735
|
+
```
|
|
1736
|
+
|
|
1737
|
+
### Type Generation API
|
|
1738
|
+
|
|
1739
|
+
```typescript
|
|
1740
|
+
import {
|
|
1741
|
+
generateDeclaration,
|
|
1742
|
+
generateModule,
|
|
1743
|
+
generateJsonSchema,
|
|
1744
|
+
createTypegenWatcher,
|
|
1745
|
+
} from 'ultraenv';
|
|
1746
|
+
```
|
|
1747
|
+
|
|
1748
|
+
### Environment Management API
|
|
1749
|
+
|
|
1750
|
+
```typescript
|
|
1751
|
+
import {
|
|
1752
|
+
listEnvironments,
|
|
1753
|
+
validateAllEnvironments,
|
|
1754
|
+
switchEnvironment,
|
|
1755
|
+
getActiveEnvironment,
|
|
1756
|
+
discoverEnvironments,
|
|
1757
|
+
compareEnvironments,
|
|
1758
|
+
formatComparison,
|
|
1759
|
+
createEnvironment,
|
|
1760
|
+
removeEnvironment,
|
|
1761
|
+
duplicateEnvironment,
|
|
1762
|
+
} from 'ultraenv';
|
|
1763
|
+
```
|
|
1764
|
+
|
|
1765
|
+
### Parser API (Advanced)
|
|
1766
|
+
|
|
1767
|
+
```typescript
|
|
1768
|
+
import {
|
|
1769
|
+
parseEnvFile,
|
|
1770
|
+
expandVariables,
|
|
1771
|
+
resolveCascade,
|
|
1772
|
+
mergeCascade,
|
|
1773
|
+
} from 'ultraenv';
|
|
1774
|
+
|
|
1775
|
+
// Parse raw .env content
|
|
1776
|
+
const parsed = parseEnvFile(content, filePath);
|
|
1777
|
+
// โ { path, vars: [{ key, value, raw, source, lineNumber, comment }], exists }
|
|
1778
|
+
|
|
1779
|
+
// Expand variable references
|
|
1780
|
+
const expanded = expandVariables(env, env, { maxDepth: 10 });
|
|
1781
|
+
```
|
|
1782
|
+
|
|
1783
|
+
> ๐ See [docs/API_REFERENCE.md](docs/API_REFERENCE.md) for the complete API documentation.
|
|
1784
|
+
|
|
1785
|
+
---
|
|
1786
|
+
|
|
1787
|
+
## ๐ Migration from dotenv
|
|
1788
|
+
|
|
1789
|
+
ultraenv is a **drop-in replacement** for dotenv. Migration takes just minutes.
|
|
1790
|
+
|
|
1791
|
+
### Before (dotenv)
|
|
1792
|
+
|
|
1793
|
+
```typescript
|
|
1794
|
+
// Load .env
|
|
1795
|
+
import dotenv from 'dotenv';
|
|
1796
|
+
dotenv.config();
|
|
1797
|
+
|
|
1798
|
+
// Use env vars (always strings!)
|
|
1799
|
+
const port = parseInt(process.env.PORT || '3000', 10);
|
|
1800
|
+
const dbUrl = process.env.DATABASE_URL!;
|
|
1801
|
+
const debug = process.env.DEBUG === 'true';
|
|
1802
|
+
```
|
|
1803
|
+
|
|
1804
|
+
### After (ultraenv)
|
|
1805
|
+
|
|
1806
|
+
```typescript
|
|
1807
|
+
// Option 1: Simple drop-in replacement
|
|
1808
|
+
import { load } from 'ultraenv';
|
|
1809
|
+
load(); // That's it!
|
|
1810
|
+
|
|
1811
|
+
// Option 2: Full type safety
|
|
1812
|
+
import { defineEnv, t } from 'ultraenv';
|
|
1813
|
+
|
|
1814
|
+
const env = defineEnv({
|
|
1815
|
+
PORT: t.number().port().default(3000),
|
|
1816
|
+
DATABASE_URL: t.string().format('url').required(),
|
|
1817
|
+
DEBUG: t.boolean().default(false),
|
|
1818
|
+
});
|
|
1819
|
+
|
|
1820
|
+
// Now everything is typed!
|
|
1821
|
+
const port = env.PORT; // number
|
|
1822
|
+
const dbUrl = env.DATABASE_URL; // string
|
|
1823
|
+
const debug = env.DEBUG; // boolean
|
|
1824
|
+
```
|
|
1825
|
+
|
|
1826
|
+
### Migration Checklist
|
|
1827
|
+
|
|
1828
|
+
- [ ] Replace `import dotenv from 'dotenv'` with `import { load } from 'ultraenv'`
|
|
1829
|
+
- [ ] Replace `dotenv.config()` with `load()`
|
|
1830
|
+
- [ ] (Optional) Add a schema with `defineEnv()` for type safety
|
|
1831
|
+
- [ ] (Optional) Replace all `process.env.X` references with typed `env.X`
|
|
1832
|
+
- [ ] (Optional) Run `ultraenv scan` to find leaked secrets
|
|
1833
|
+
- [ ] (Optional) Run `ultraenv sync` to generate `.env.example`
|
|
1834
|
+
- [ ] (Optional) Set up `ultraenv ci setup` for CI/CD
|
|
1835
|
+
|
|
1836
|
+
> ๐ See [docs/MIGRATION_FROM_DOTENV.md](docs/MIGRATION_FROM_DOTENV.md) for the complete migration guide.
|
|
1837
|
+
|
|
1838
|
+
---
|
|
1839
|
+
|
|
1840
|
+
## โ๏ธ Configuration Reference
|
|
1841
|
+
|
|
1842
|
+
### Config File Locations (searched in order)
|
|
1843
|
+
|
|
1844
|
+
1. `.ultraenvrc.json`
|
|
1845
|
+
2. `.ultraenvrc.yaml`
|
|
1846
|
+
3. `.ultraenvrc.yml`
|
|
1847
|
+
4. `ultraenv.config.js`
|
|
1848
|
+
5. `ultraenv.config.cjs`
|
|
1849
|
+
6. `"ultraenv"` key in `package.json`
|
|
1850
|
+
|
|
1851
|
+
### Example Configuration
|
|
1852
|
+
|
|
1853
|
+
```json
|
|
1854
|
+
{
|
|
1855
|
+
"envDir": ".",
|
|
1856
|
+
"files": [".env", ".env.local", ".env.production"],
|
|
1857
|
+
"encoding": "utf-8",
|
|
1858
|
+
"expandVariables": true,
|
|
1859
|
+
"overrideProcessEnv": false,
|
|
1860
|
+
"mergeStrategy": "last-wins",
|
|
1861
|
+
"prefixErrors": true,
|
|
1862
|
+
"silent": false,
|
|
1863
|
+
"outputFormat": "terminal",
|
|
1864
|
+
"debug": false,
|
|
1865
|
+
"maxValueLength": 1048576,
|
|
1866
|
+
"maxInterpolationDepth": 10,
|
|
1867
|
+
"schema": {
|
|
1868
|
+
"PORT": { "type": "number", "port": true, "default": 3000 },
|
|
1869
|
+
"DATABASE_URL": { "type": "string", "format": "url" }
|
|
1870
|
+
},
|
|
1871
|
+
"vault": {
|
|
1872
|
+
"vaultFile": ".env.vault",
|
|
1873
|
+
"keysFile": ".env.keys",
|
|
1874
|
+
"environment": "production",
|
|
1875
|
+
"autoGenerateKey": true
|
|
1876
|
+
},
|
|
1877
|
+
"scan": {
|
|
1878
|
+
"include": ["src/", "config/"],
|
|
1879
|
+
"exclude": ["**/*.test.ts", "node_modules/**"],
|
|
1880
|
+
"scanGitHistory": false,
|
|
1881
|
+
"maxFileSize": 1048576,
|
|
1882
|
+
"failFast": false
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
```
|
|
1886
|
+
|
|
1887
|
+
### Configuration Options
|
|
1888
|
+
|
|
1889
|
+
| Option | Type | Default | Description |
|
|
1890
|
+
|---|---|---|---|
|
|
1891
|
+
| `envDir` | `string` | `'.'` | Directory containing `.env` files |
|
|
1892
|
+
| `files` | `string[]` | `[]` | Specific `.env` files to load |
|
|
1893
|
+
| `encoding` | `string` | `'utf-8'` | File encoding |
|
|
1894
|
+
| `expandVariables` | `boolean` | `true` | Expand `$VAR` references |
|
|
1895
|
+
| `overrideProcessEnv` | `boolean` | `false` | Override existing `process.env` values |
|
|
1896
|
+
| `mergeStrategy` | `string` | `'last-wins'` | Merge strategy: `first-wins`, `last-wins`, `error-on-conflict` |
|
|
1897
|
+
| `prefixErrors` | `boolean` | `true` | Prefix error messages with "ultraenv" |
|
|
1898
|
+
| `silent` | `boolean` | `false` | Suppress all output |
|
|
1899
|
+
| `outputFormat` | `string` | `'terminal'` | Output format: `terminal`, `json`, `silent` |
|
|
1900
|
+
| `debug` | `boolean` | `false` | Enable debug logging |
|
|
1901
|
+
| `maxValueLength` | `number` | `1048576` | Max value length in bytes (1 MB) |
|
|
1902
|
+
| `maxInterpolationDepth` | `number` | `10` | Max variable expansion depth |
|
|
1903
|
+
|
|
1904
|
+
---
|
|
1905
|
+
|
|
1906
|
+
## โ FAQ
|
|
1907
|
+
|
|
1908
|
+
### General
|
|
1909
|
+
|
|
1910
|
+
**Q: Is ultraenv really zero dependencies?**
|
|
1911
|
+
|
|
1912
|
+
A: Yes! ultraenv has zero runtime dependencies. The entire library (parser, validator, scanner, vault, CLI, typegen) is built with Node.js built-in modules only. Dev dependencies (TypeScript, Vitest, ESLint, tsup) are only used during development.
|
|
1913
|
+
|
|
1914
|
+
**Q: Does it work with ESM and CommonJS?**
|
|
1915
|
+
|
|
1916
|
+
A: Yes! ultraenv ships dual CJS/ESM builds:
|
|
1917
|
+
- `import { load } from 'ultraenv'` (ESM)
|
|
1918
|
+
- `const { load } = require('ultraenv')` (CJS)
|
|
1919
|
+
|
|
1920
|
+
**Q: What Node.js versions are supported?**
|
|
1921
|
+
|
|
1922
|
+
A: Node.js >= 18.0.0. This aligns with the current Node.js LTS cycle.
|
|
1923
|
+
|
|
1924
|
+
### Schema & Validation
|
|
1925
|
+
|
|
1926
|
+
**Q: Can I use zod schemas with ultraenv?**
|
|
1927
|
+
|
|
1928
|
+
A: ultraenv has its own schema engine that's purpose-built for env vars. It's lighter and has env-specific validators (port, URL, duration, bytes, etc.) that zod doesn't provide out of the box. However, you can convert between them if needed.
|
|
1929
|
+
|
|
1930
|
+
**Q: What happens when validation fails?**
|
|
1931
|
+
|
|
1932
|
+
A: `defineEnv()` throws a detailed error with all validation failures. Use `tryDefineEnv()` for a non-throwing variant that returns `{ valid, errors, warnings, values }`.
|
|
1933
|
+
|
|
1934
|
+
**Q: Can I validate without throwing?**
|
|
1935
|
+
|
|
1936
|
+
A: Yes! Use `tryDefineEnv()`:
|
|
1937
|
+
|
|
1938
|
+
```typescript
|
|
1939
|
+
const result = tryDefineEnv(schema);
|
|
1940
|
+
if (!result.valid) {
|
|
1941
|
+
console.log(result.errors);
|
|
1942
|
+
}
|
|
1943
|
+
```
|
|
1944
|
+
|
|
1945
|
+
**Q: How do default values work?**
|
|
1946
|
+
|
|
1947
|
+
A: If a variable is not set in the environment, the default value is used. If no default is set and the variable is not optional, validation fails.
|
|
1948
|
+
|
|
1949
|
+
### Vault & Security
|
|
1950
|
+
|
|
1951
|
+
**Q: Is the vault safe to commit to git?**
|
|
1952
|
+
|
|
1953
|
+
A: Yes! The `.env.vault` file contains only AES-256-GCM encrypted data. Without the decryption key (stored in `.env.keys`, which is gitignored), the vault contents are unreadable.
|
|
1954
|
+
|
|
1955
|
+
**Q: What if I lose my encryption key?**
|
|
1956
|
+
|
|
1957
|
+
A: You cannot recover encrypted values without the key. This is by design. Use `ultraenv vault rekey` to rotate keys, and always store keys in a secure secrets manager (e.g., GitHub Secrets, AWS Secrets Manager, HashiCorp Vault).
|
|
1958
|
+
|
|
1959
|
+
**Q: How does key rotation work?**
|
|
1960
|
+
|
|
1961
|
+
A: `ultraenv vault rekey` generates a new key and re-encrypts all values in the vault. Update your `.env.keys` file and CI secrets with the new key.
|
|
1962
|
+
|
|
1963
|
+
### Secret Scanning
|
|
1964
|
+
|
|
1965
|
+
**Q: How accurate is the secret scanner?**
|
|
1966
|
+
|
|
1967
|
+
A: The scanner uses a combination of regex pattern matching (55+ patterns) and Shannon entropy analysis. Each pattern has a confidence score. High-confidence patterns (>0.9) are very accurate; lower-confidence patterns may have false positives.
|
|
1968
|
+
|
|
1969
|
+
**Q: Can I add custom patterns?**
|
|
1970
|
+
|
|
1971
|
+
A: Yes! Use `addCustomPattern()`:
|
|
1972
|
+
|
|
1973
|
+
```typescript
|
|
1974
|
+
import { addCustomPattern } from 'ultraenv';
|
|
1975
|
+
addCustomPattern({
|
|
1976
|
+
id: 'my-company-token',
|
|
1977
|
+
name: 'My Company Token',
|
|
1978
|
+
pattern: /MYCO_[A-Za-z0-9]{32}/g,
|
|
1979
|
+
confidence: 0.9,
|
|
1980
|
+
severity: 'critical',
|
|
1981
|
+
description: 'Company internal API token',
|
|
1982
|
+
remediation: 'Rotate and store in vault.',
|
|
1983
|
+
category: 'internal',
|
|
1984
|
+
});
|
|
1985
|
+
```
|
|
1986
|
+
|
|
1987
|
+
**Q: Can I suppress false positives?**
|
|
1988
|
+
|
|
1989
|
+
A: Add a comment `// ultraenv-ignore` or `# ultraenv-ignore` on the line before or on the same line as the detected secret.
|
|
1990
|
+
|
|
1991
|
+
### CI/CD
|
|
1992
|
+
|
|
1993
|
+
**Q: How do I use ultraenv in CI?**
|
|
1994
|
+
|
|
1995
|
+
A: Run `ultraenv ci setup --platform github` or `--platform gitlab` to generate CI config files. Or manually add the CI commands to your pipeline.
|
|
1996
|
+
|
|
1997
|
+
**Q: Does ultraenv scan integrate with GitHub Code Scanning?**
|
|
1998
|
+
|
|
1999
|
+
A: Yes! Use `ultraenv scan --format sarif --output results.sarif` and upload with the `github/codeql-action/upload-sarif` action.
|
|
2000
|
+
|
|
2001
|
+
---
|
|
2002
|
+
|
|
2003
|
+
## ๐ค Contributing
|
|
2004
|
+
|
|
2005
|
+
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
2006
|
+
|
|
2007
|
+
### Quick Start
|
|
2008
|
+
|
|
2009
|
+
1. Fork the repository
|
|
2010
|
+
2. Clone your fork: `git clone https://github.com/your-username/ultraenv.git`
|
|
2011
|
+
3. Install dependencies: `npm install`
|
|
2012
|
+
4. Build: `npm run build`
|
|
2013
|
+
5. Run tests: `npm test`
|
|
2014
|
+
6. Run linter: `npm run lint`
|
|
2015
|
+
|
|
2016
|
+
### Development Scripts
|
|
2017
|
+
|
|
2018
|
+
| Script | Description |
|
|
2019
|
+
|---|---|
|
|
2020
|
+
| `npm run build` | Build the library |
|
|
2021
|
+
| `npm run dev` | Build in watch mode |
|
|
2022
|
+
| `npm test` | Run tests |
|
|
2023
|
+
| `npm run test:watch` | Run tests in watch mode |
|
|
2024
|
+
| `npm run test:coverage` | Run tests with coverage |
|
|
2025
|
+
| `npm run lint` | Run ESLint |
|
|
2026
|
+
| `npm run lint:fix` | Fix ESLint issues |
|
|
2027
|
+
| `npm run format` | Format with Prettier |
|
|
2028
|
+
| `npm run typecheck` | TypeScript type checking |
|
|
2029
|
+
|
|
2030
|
+
---
|
|
2031
|
+
|
|
2032
|
+
## ๐ License
|
|
2033
|
+
|
|
2034
|
+
[MIT](LICENSE) ยฉ 2024 [Avinash Velu](https://github.com/Avinashvelu03)
|
|
2035
|
+
|
|
2036
|
+
---
|
|
2037
|
+
|
|
2038
|
+
## ๐ Acknowledgments
|
|
2039
|
+
|
|
2040
|
+
- [dotenv](https://github.com/motdotla/dotenv) โ Inspiration for the core `.env` parsing
|
|
2041
|
+
- [envalid](https://github.com/af/envalid) โ Inspiration for schema-based validation
|
|
2042
|
+
- [@t3-oss/env](https://github.com/t3-oss/env-core) โ Inspiration for TypeScript inference
|
|
2043
|
+
- [gitleaks](https://github.com/gitleaks/gitleaks) โ Inspiration for secret scanning patterns
|
|
2044
|
+
- [trufflehog](https://github.com/trufflesecurity/trufflehog) โ Inspiration for secret detection
|
|
2045
|
+
|
|
2046
|
+
---
|
|
2047
|
+
|
|
2048
|
+
## โญ Star History
|
|
2049
|
+
|
|
2050
|
+
If you find ultraenv useful, please consider giving it a star on [GitHub](https://github.com/Avinashvelu03/ultraenv). It helps the project grow and reach more developers!
|
|
2051
|
+
|
|
2052
|
+
<div align="center">
|
|
2053
|
+
|
|
2054
|
+
**Made with โค๏ธ by [Avinash Velu](https://github.com/Avinashvelu03)**
|
|
2055
|
+
|
|
2056
|
+
[Report Bug](https://github.com/Avinashvelu03/ultraenv/issues) ยท [Request Feature](https://github.com/Avinashvelu03/ultraenv/issues) ยท [Discussions](https://github.com/Avinashvelu03/ultraenv/discussions)
|
|
2057
|
+
|
|
2058
|
+
</div>
|