env-typed-checker 0.1.1 β 0.2.1
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/README.md +224 -72
- package/bin/env-typed-checker.cjs +6 -0
- package/dist/chunk-L5DK6LRX.mjs +262 -0
- package/dist/chunk-L5DK6LRX.mjs.map +1 -0
- package/dist/cli/index.d.mts +7 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.js +479 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.mjs +190 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/index.d.mts +31 -16
- package/dist/index.d.ts +31 -16
- package/dist/index.js +174 -37
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4 -120
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,34 +1,47 @@
|
|
|
1
|
-
# env-typed-checker
|
|
1
|
+
# env-typed-checker
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Validate and parse environment variables using a tiny schema β with both a **TypeScript/Node API** and a **CLI**.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
It helps your app fail fast when configuration is wrong:
|
|
6
|
+
|
|
7
|
+
- β Missing required variables
|
|
8
|
+
- β Wrong types (e.g. `PORT="abc"`)
|
|
9
|
+
- β Invalid URLs / emails / JSON
|
|
10
|
+
- β Values not matching allowed enums or regex patterns
|
|
11
|
+
- β
Optional values + defaults
|
|
12
|
+
- β
CLI checks for CI + .env generation
|
|
6
13
|
|
|
7
|
-
- β missing environment variables
|
|
8
|
-
- β wrong types (e.g. PORT="abc")
|
|
9
|
-
- β invalid URLs or JSON
|
|
10
|
-
- β silent configuration mistakes
|
|
11
14
|
|
|
12
15
|
---
|
|
13
16
|
|
|
14
17
|
## β¨ Features
|
|
15
18
|
|
|
16
|
-
- Simple schema syntax
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
-
|
|
19
|
+
- Simple schema syntax (`"number"`, `"boolean?"`, `"email"` `"url"`, `"json"`, `"string"`)
|
|
20
|
+
- Advanced specs: `enum` and `regex`
|
|
21
|
+
- Optional values with `?` and `optional: true`
|
|
22
|
+
- Defaults (typed + validated)
|
|
23
|
+
- CLI:
|
|
24
|
+
|
|
25
|
+
- `check` β validate env
|
|
26
|
+
- `generate` β generate/update `.env` from schema (no overwrite by default)
|
|
27
|
+
- Uses `.env` via `dotenv` (optional)
|
|
28
|
+
- Friendly aggregated errors (see everything thatβs wrong at once)
|
|
23
29
|
|
|
24
30
|
---
|
|
25
31
|
|
|
26
|
-
## π¦
|
|
32
|
+
## π¦ Install
|
|
27
33
|
|
|
28
34
|
```bash
|
|
29
35
|
npm install env-typed-checker
|
|
30
36
|
```
|
|
31
|
-
|
|
37
|
+
|
|
38
|
+
Or run via npx:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx env-typed-checker --help
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## π Quick Start (Code)
|
|
32
45
|
|
|
33
46
|
```ts
|
|
34
47
|
import { envDoctor } from "env-typed-checker";
|
|
@@ -36,13 +49,22 @@ import { envDoctor } from "env-typed-checker";
|
|
|
36
49
|
export const config = envDoctor({
|
|
37
50
|
PORT: "number",
|
|
38
51
|
DB_URL: "url",
|
|
39
|
-
|
|
52
|
+
ADMIN_EMAIL: "email",
|
|
53
|
+
DEBUG: "boolean?",
|
|
40
54
|
});
|
|
55
|
+
|
|
41
56
|
```
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
*
|
|
57
|
+
|
|
58
|
+
### What you get
|
|
59
|
+
|
|
60
|
+
* PORT β number
|
|
61
|
+
|
|
62
|
+
* DB_URL β string (validated as URL)
|
|
63
|
+
|
|
64
|
+
* ADMIN_EMAIL β string (validated as email)
|
|
65
|
+
|
|
66
|
+
* DEBUG β boolean | undefined (optional)
|
|
67
|
+
|
|
46
68
|
|
|
47
69
|
### π§© Supported Types
|
|
48
70
|
| Type | Description |
|
|
@@ -52,87 +74,225 @@ export const config = envDoctor({
|
|
|
52
74
|
| **boolean** | Supports `true` / `false`, `1` / `0`, and `yes` / `no` |
|
|
53
75
|
| **json** | Validates and parses a valid JSON string |
|
|
54
76
|
| **url** | Validates for a properly formatted URL |
|
|
77
|
+
| **email** | Validates for a properly formatted email |
|
|
78
|
+
|
|
55
79
|
|
|
56
80
|
### Optional Values
|
|
57
81
|
Add ? to make a variable optional:
|
|
58
82
|
```ts
|
|
59
|
-
{ DEBUG: "boolean?" }
|
|
83
|
+
envDoctor({ DEBUG: "boolean?" });
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Or use object-style:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
envDoctor({
|
|
90
|
+
DEBUG: { type: "boolean", optional: true },
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
Missing optional vars become `undefined` unless you provide a default.
|
|
94
|
+
|
|
95
|
+
### π― Defaults
|
|
96
|
+
Defaults can be provided in object-style specs.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { envDoctor } from "env-typed-checker";
|
|
100
|
+
|
|
101
|
+
const config = envDoctor({
|
|
102
|
+
PORT: { type: "number", default: 3000 },
|
|
103
|
+
DEBUG: { type: "boolean", default: "false" }, // string is allowed (parsed)
|
|
104
|
+
NODE_ENV: { type: "enum", values: ["dev", "prod"], default: "dev" },
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Notes:
|
|
109
|
+
|
|
110
|
+
- `number` default: number or string (string will be parsed)
|
|
111
|
+
- `boolean` default: boolean or string (string will be parsed)
|
|
112
|
+
- `url/email` defaults must be strings and must validate
|
|
113
|
+
- `json` defaults can be any JSON-like value
|
|
114
|
+
|
|
115
|
+
### β
Enum and Regex
|
|
116
|
+
|
|
117
|
+
### Enum
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
const config = envDoctor({
|
|
121
|
+
NODE_ENV: { type: "enum", values: ["dev", "prod"] },
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Regex
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const config = envDoctor({
|
|
129
|
+
SLUG: { type: "regex", pattern: "^[a-z0-9-]+$", flags: "i" },
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### βοΈ Options
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
envDoctor(schema, {
|
|
137
|
+
loadDotEnv: true, // default: true (loads .env)
|
|
138
|
+
env: process.env // default: process.env (override for tests)
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
### π§ͺ Testing with custom env
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { envDoctor } from "env-typed-checker";
|
|
147
|
+
|
|
148
|
+
const cfg = envDoctor(
|
|
149
|
+
{ PORT: "number" },
|
|
150
|
+
{ loadDotEnv: false, env: { PORT: "3000" } }
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
console.log(cfg.PORT); // 3000
|
|
60
154
|
```
|
|
61
|
-
If missing β value will be undefined.
|
|
62
155
|
|
|
63
156
|
### β Error Example
|
|
64
|
-
|
|
65
|
-
|
|
157
|
+
|
|
158
|
+
Given a `.env` like:
|
|
159
|
+
|
|
160
|
+
```env
|
|
66
161
|
PORT=abc
|
|
67
162
|
DB_URL=not-a-url
|
|
68
|
-
|
|
163
|
+
NODE_ENV=staging
|
|
69
164
|
```
|
|
165
|
+
|
|
70
166
|
```ts
|
|
167
|
+
import { envDoctor } from "env-typed-checker";
|
|
168
|
+
|
|
71
169
|
envDoctor({
|
|
72
170
|
PORT: "number",
|
|
73
171
|
DB_URL: "url"
|
|
172
|
+
NODE_ENV: { type: "enum", values: ["dev", "prod"] },
|
|
74
173
|
});
|
|
75
174
|
```
|
|
175
|
+
|
|
76
176
|
### Output:
|
|
77
177
|
|
|
78
178
|
```ts
|
|
79
179
|
ENV validation failed
|
|
80
180
|
- PORT: expected number, got "abc"
|
|
81
181
|
- DB_URL: expected url, got "not-a-url"
|
|
182
|
+
- NODE_ENV: must be one of [dev, prod]
|
|
82
183
|
```
|
|
83
184
|
All errors are shown together so you can fix them in one go.
|
|
84
185
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
186
|
+
# π₯οΈ CLI
|
|
187
|
+
|
|
188
|
+
Validate your environment without writing code β perfect for CI pipelines.
|
|
189
|
+
|
|
190
|
+
## 1) Create a schema file
|
|
191
|
+
|
|
192
|
+
`env.schema.json`
|
|
193
|
+
```json
|
|
194
|
+
{
|
|
195
|
+
"PORT": "number",
|
|
196
|
+
"DB_URL": "url",
|
|
197
|
+
"ADMIN_EMAIL": "email",
|
|
198
|
+
"DEBUG": "boolean?",
|
|
199
|
+
"NODE_ENV": { "type": "enum", "values": ["dev", "prod"] },
|
|
200
|
+
"SLUG": { "type": "regex", "pattern": "^[a-z0-9-]+$", "flags": "i" }
|
|
201
|
+
}
|
|
91
202
|
```
|
|
92
|
-
### π§ͺ Example with Custom Env (Testing)
|
|
93
203
|
|
|
94
|
-
|
|
95
|
-
const cfg = envDoctor(
|
|
96
|
-
{ PORT: "number" },
|
|
97
|
-
{ loadDotEnv: false, env: { PORT: "3000" } }
|
|
98
|
-
);
|
|
204
|
+
## 2) Run the check
|
|
99
205
|
|
|
100
|
-
|
|
206
|
+
```bash
|
|
207
|
+
npx env-typed-checker check --schema env.schema.json
|
|
101
208
|
```
|
|
102
|
-
|
|
209
|
+
|
|
210
|
+
Useful flags
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# Use a specific env file (instead of .env)
|
|
214
|
+
npx env-typed-checker check --schema env.schema.json --env-file .env.production
|
|
215
|
+
|
|
216
|
+
# Skip dotenv loading and validate only process.env
|
|
217
|
+
npx env-typed-checker check --schema env.schema.json --no-dotenv
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### `generate` β generate or update `.env`
|
|
221
|
+
|
|
222
|
+
Generate values from schema (writes missing keys; does not overwrite existing values).
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
npx env-typed-checker generate --schema env.schema.json
|
|
226
|
+
```
|
|
227
|
+
### By default:
|
|
228
|
+
|
|
229
|
+
- output file: .env
|
|
230
|
+
- mode: update
|
|
231
|
+
|
|
232
|
+
### Flags
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
# Custom output file
|
|
236
|
+
npx env-typed-checker generate --schema env.schema.json --out .env.example
|
|
237
|
+
|
|
238
|
+
# Create mode: refuse to overwrite an existing file
|
|
239
|
+
npx env-typed-checker generate --schema env.schema.json --out .env --mode=create
|
|
240
|
+
|
|
241
|
+
# Update mode: append only missing keys (safe)
|
|
242
|
+
npx env-typed-checker generate --schema env.schema.json --out .env --mode=update
|
|
243
|
+
|
|
244
|
+
# Do not write defaults (leave blank values)
|
|
245
|
+
npx env-typed-checker generate --schema env.schema.json --no-defaults
|
|
246
|
+
|
|
247
|
+
# Add inline type comments (useful for .env.example)
|
|
248
|
+
npx env-typed-checker generate --schema env.schema.json --comment-types
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Exit codes
|
|
252
|
+
|
|
253
|
+
* `0` = OK
|
|
254
|
+
|
|
255
|
+
* `1` = validation failed
|
|
256
|
+
|
|
257
|
+
* `2` = CLI usage / unexpected error
|
|
258
|
+
|
|
259
|
+
## β
CI Example (GitHub Actions)
|
|
260
|
+
|
|
261
|
+
Add this to your workflow to fail the build if env is invalid:
|
|
262
|
+
|
|
263
|
+
```yml
|
|
264
|
+
- name: Validate env
|
|
265
|
+
run: npx env-typed-checker check --schema env.schema.json
|
|
266
|
+
```
|
|
267
|
+
If you use a specific env file in CI:
|
|
268
|
+
|
|
269
|
+
```yml
|
|
270
|
+
- name: Validate env
|
|
271
|
+
run: npx env-typed-checker check --schema env.schema.json --env-file .env.ci
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## π Development
|
|
103
275
|
Clone the repo and install:
|
|
104
276
|
```bash
|
|
105
277
|
npm install
|
|
278
|
+
npm test
|
|
106
279
|
```
|
|
107
|
-
###
|
|
280
|
+
### Common scripts:
|
|
108
281
|
```bash
|
|
109
282
|
npm run build # build package
|
|
110
283
|
npm run test # run tests
|
|
111
284
|
npm run typecheck # TypeScript check
|
|
112
285
|
npm run dev # watch build
|
|
113
286
|
```
|
|
287
|
+
|
|
114
288
|
### π€ Contributing
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
* Improve
|
|
118
|
-
* Add more
|
|
119
|
-
*
|
|
120
|
-
* Add
|
|
121
|
-
|
|
122
|
-
* Please read CONTRIBUTING.md before opening a PR.
|
|
123
|
-
|
|
124
|
-
### π Roadmap
|
|
125
|
-
#### v1 (current)
|
|
126
|
-
* Schema validation
|
|
127
|
-
* Type parsing
|
|
128
|
-
* Optional values
|
|
129
|
-
* Friendly errors
|
|
130
|
-
|
|
131
|
-
#### v2 (planned)
|
|
132
|
-
* CLI support
|
|
133
|
-
* .env.example generator
|
|
134
|
-
* Strict unknown variable check
|
|
135
|
-
* Framework integrations
|
|
289
|
+
PRs are welcome!
|
|
290
|
+
|
|
291
|
+
* Improve docs / examples
|
|
292
|
+
* Add more schema features
|
|
293
|
+
* Improve CLI output formatting
|
|
294
|
+
* Add integrations / templates
|
|
295
|
+
|
|
136
296
|
|
|
137
297
|
# π License
|
|
138
298
|
MIT
|
|
@@ -141,13 +301,5 @@ MIT
|
|
|
141
301
|
---
|
|
142
302
|
|
|
143
303
|
```yml
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
- badges (npm version, CI, coverage)
|
|
147
|
-
- a small logo
|
|
148
|
-
- example project section
|
|
149
|
-
|
|
150
|
-
Just tell me π
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
|
|
304
|
+
::contentReference[oaicite:0]{index=0}
|
|
305
|
+
```
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// src/core/envDoctor.ts
|
|
2
|
+
import * as dotenv from "dotenv";
|
|
3
|
+
|
|
4
|
+
// src/errors/EnvDoctorError.ts
|
|
5
|
+
var EnvDoctorError = class extends Error {
|
|
6
|
+
constructor(issues) {
|
|
7
|
+
const header = "ENV validation failed";
|
|
8
|
+
const lines = issues.map((i) => `- ${i.key}: ${i.message}`);
|
|
9
|
+
super([header, ...lines].join("\n"));
|
|
10
|
+
this.name = "EnvDoctorError";
|
|
11
|
+
this.issues = issues;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/validators/primitives.ts
|
|
16
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
17
|
+
function parseByType(type, raw) {
|
|
18
|
+
switch (type) {
|
|
19
|
+
case "string":
|
|
20
|
+
return raw;
|
|
21
|
+
case "number": {
|
|
22
|
+
const n = Number(raw.trim());
|
|
23
|
+
if (!Number.isFinite(n)) throw new Error(`expected number, got "${raw}"`);
|
|
24
|
+
return n;
|
|
25
|
+
}
|
|
26
|
+
case "boolean": {
|
|
27
|
+
const v = raw.trim().toLowerCase();
|
|
28
|
+
if (["true", "1", "yes", "y", "on"].includes(v)) return true;
|
|
29
|
+
if (["false", "0", "no", "n", "off"].includes(v)) return false;
|
|
30
|
+
throw new Error(
|
|
31
|
+
`expected boolean (true/false/1/0/yes/no/on/off), got "${raw}"`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
case "json": {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(raw);
|
|
37
|
+
} catch {
|
|
38
|
+
throw new Error(`expected json, got "${raw}"`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
case "url": {
|
|
42
|
+
try {
|
|
43
|
+
new URL(raw);
|
|
44
|
+
return raw;
|
|
45
|
+
} catch {
|
|
46
|
+
throw new Error(`expected url, got "${raw}"`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
case "email": {
|
|
50
|
+
const s = raw.trim();
|
|
51
|
+
if (!EMAIL_RE.test(s)) {
|
|
52
|
+
throw new Error(`expected email, got "${raw}"`);
|
|
53
|
+
}
|
|
54
|
+
return s;
|
|
55
|
+
}
|
|
56
|
+
default: {
|
|
57
|
+
throw new Error(`unsupported primitive type: ${String(type)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function hasOwn(obj, key) {
|
|
62
|
+
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
63
|
+
}
|
|
64
|
+
function coerceDefault(kind, def) {
|
|
65
|
+
if (def === void 0) return void 0;
|
|
66
|
+
switch (kind) {
|
|
67
|
+
case "string": {
|
|
68
|
+
if (typeof def !== "string")
|
|
69
|
+
throw new Error(`default for string must be a string`);
|
|
70
|
+
return def;
|
|
71
|
+
}
|
|
72
|
+
case "number": {
|
|
73
|
+
if (typeof def === "number") {
|
|
74
|
+
if (!Number.isFinite(def))
|
|
75
|
+
throw new Error(`default for number must be finite`);
|
|
76
|
+
return def;
|
|
77
|
+
}
|
|
78
|
+
if (typeof def === "string") return parseByType("number", def);
|
|
79
|
+
throw new Error(`default for number must be number or string`);
|
|
80
|
+
}
|
|
81
|
+
case "boolean": {
|
|
82
|
+
if (typeof def === "boolean") return def;
|
|
83
|
+
if (typeof def === "string") return parseByType("boolean", def);
|
|
84
|
+
throw new Error(`default for boolean must be boolean or string`);
|
|
85
|
+
}
|
|
86
|
+
case "json": {
|
|
87
|
+
return def;
|
|
88
|
+
}
|
|
89
|
+
case "url": {
|
|
90
|
+
if (typeof def !== "string")
|
|
91
|
+
throw new Error(`default for url must be a string`);
|
|
92
|
+
return parseByType("url", def);
|
|
93
|
+
}
|
|
94
|
+
case "email": {
|
|
95
|
+
if (typeof def !== "string")
|
|
96
|
+
throw new Error(`default for email must be a string`);
|
|
97
|
+
return parseByType("email", def);
|
|
98
|
+
}
|
|
99
|
+
/* c8 ignore next */
|
|
100
|
+
default: {
|
|
101
|
+
throw new Error(`unsupported default kind: ${String(kind)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function normalizeSpec(schemaValue) {
|
|
106
|
+
if (typeof schemaValue === "string") {
|
|
107
|
+
const optional = schemaValue.endsWith("?");
|
|
108
|
+
const base = optional ? schemaValue.slice(0, -1) : schemaValue;
|
|
109
|
+
const allowed = [
|
|
110
|
+
"string",
|
|
111
|
+
"number",
|
|
112
|
+
"boolean",
|
|
113
|
+
"json",
|
|
114
|
+
"url",
|
|
115
|
+
"email"
|
|
116
|
+
];
|
|
117
|
+
if (!allowed.includes(base)) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Unsupported type "${schemaValue}". Supported: string, number, boolean, json, url, email (optional with ?)`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return { kind: base, optional };
|
|
123
|
+
}
|
|
124
|
+
if (!schemaValue || typeof schemaValue !== "object" || Array.isArray(schemaValue)) {
|
|
125
|
+
throw new Error("Schema value must be a string or object spec.");
|
|
126
|
+
}
|
|
127
|
+
const t = schemaValue.type;
|
|
128
|
+
const primitiveAllowed = [
|
|
129
|
+
"string",
|
|
130
|
+
"number",
|
|
131
|
+
"boolean",
|
|
132
|
+
"json",
|
|
133
|
+
"url",
|
|
134
|
+
"email"
|
|
135
|
+
];
|
|
136
|
+
if (primitiveAllowed.includes(t)) {
|
|
137
|
+
const optional = !!schemaValue.optional;
|
|
138
|
+
const defaultValue = hasOwn(schemaValue, "default") ? coerceDefault(t, schemaValue.default) : void 0;
|
|
139
|
+
return { kind: t, optional, defaultValue };
|
|
140
|
+
}
|
|
141
|
+
if (t === "enum") {
|
|
142
|
+
const values = schemaValue.values;
|
|
143
|
+
if (!Array.isArray(values) || values.length === 0 || !values.every((v) => typeof v === "string")) {
|
|
144
|
+
throw new Error(`enum spec requires "values": string[] (non-empty)`);
|
|
145
|
+
}
|
|
146
|
+
const optional = !!schemaValue.optional;
|
|
147
|
+
let defaultValue = void 0;
|
|
148
|
+
if (hasOwn(schemaValue, "default")) {
|
|
149
|
+
const def = schemaValue.default;
|
|
150
|
+
if (typeof def !== "string") {
|
|
151
|
+
throw new Error(`default for enum must be a string`);
|
|
152
|
+
}
|
|
153
|
+
if (!values.includes(def)) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`default "${def}" must be one of [${values.join(", ")}]`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
defaultValue = def;
|
|
159
|
+
}
|
|
160
|
+
return { kind: "enum", optional, values, defaultValue };
|
|
161
|
+
}
|
|
162
|
+
if (t === "regex") {
|
|
163
|
+
const pattern = schemaValue.pattern;
|
|
164
|
+
const flags = schemaValue.flags;
|
|
165
|
+
if (typeof pattern !== "string" || pattern.length === 0) {
|
|
166
|
+
throw new Error(`regex spec requires "pattern": string`);
|
|
167
|
+
}
|
|
168
|
+
if (flags !== void 0 && typeof flags !== "string") {
|
|
169
|
+
throw new Error(`regex spec "flags" must be a string if provided`);
|
|
170
|
+
}
|
|
171
|
+
let re;
|
|
172
|
+
try {
|
|
173
|
+
re = new RegExp(pattern, flags);
|
|
174
|
+
} catch (e) {
|
|
175
|
+
throw new Error(`invalid regex: ${String(e)}`);
|
|
176
|
+
}
|
|
177
|
+
const display = `/${pattern}/${flags ?? ""}`;
|
|
178
|
+
const optional = !!schemaValue.optional;
|
|
179
|
+
let defaultValue = void 0;
|
|
180
|
+
if (hasOwn(schemaValue, "default")) {
|
|
181
|
+
const def = schemaValue.default;
|
|
182
|
+
if (typeof def !== "string") {
|
|
183
|
+
throw new Error(`default for regex must be a string`);
|
|
184
|
+
}
|
|
185
|
+
if (!re.test(def)) {
|
|
186
|
+
throw new Error(`default "${def}" does not match ${display}`);
|
|
187
|
+
}
|
|
188
|
+
defaultValue = def;
|
|
189
|
+
}
|
|
190
|
+
return { kind: "regex", optional, re, display, defaultValue };
|
|
191
|
+
}
|
|
192
|
+
throw new Error(
|
|
193
|
+
`Unsupported object spec type "${String(t)}". Supported: primitives (string/number/boolean/json/url/email), enum, regex`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/core/runner.ts
|
|
198
|
+
function validateAndParse(schema, env) {
|
|
199
|
+
const issues = [];
|
|
200
|
+
const out = {};
|
|
201
|
+
for (const [key, schemaValue] of Object.entries(schema)) {
|
|
202
|
+
let spec;
|
|
203
|
+
try {
|
|
204
|
+
spec = normalizeSpec(schemaValue);
|
|
205
|
+
} catch (e) {
|
|
206
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
207
|
+
issues.push({ key, kind: "invalid", message: msg });
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const raw = env[key];
|
|
211
|
+
if (raw === void 0 || raw === "") {
|
|
212
|
+
if (spec.defaultValue !== void 0) {
|
|
213
|
+
out[key] = spec.defaultValue;
|
|
214
|
+
} else if (spec.optional) {
|
|
215
|
+
out[key] = void 0;
|
|
216
|
+
} else {
|
|
217
|
+
issues.push({
|
|
218
|
+
key,
|
|
219
|
+
kind: "missing",
|
|
220
|
+
message: "missing required environment variable"
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
if (spec.kind === "enum") {
|
|
227
|
+
if (!spec.values.includes(raw)) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`expected one of [${spec.values.join(", ")}], got "${raw}"`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
out[key] = raw;
|
|
233
|
+
} else if (spec.kind === "regex") {
|
|
234
|
+
if (!spec.re.test(raw)) {
|
|
235
|
+
throw new Error(`does not match ${spec.display}`);
|
|
236
|
+
}
|
|
237
|
+
out[key] = raw;
|
|
238
|
+
} else {
|
|
239
|
+
out[key] = parseByType(spec.kind, raw);
|
|
240
|
+
}
|
|
241
|
+
} catch (e) {
|
|
242
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
243
|
+
issues.push({ key, kind: "invalid", message: msg });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (issues.length > 0) throw new EnvDoctorError(issues);
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/core/envDoctor.ts
|
|
251
|
+
function envDoctor(schema, options = {}) {
|
|
252
|
+
const { loadDotEnv = true, env = process.env } = options;
|
|
253
|
+
if (loadDotEnv) dotenv.config();
|
|
254
|
+
return validateAndParse(schema, env);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export {
|
|
258
|
+
EnvDoctorError,
|
|
259
|
+
normalizeSpec,
|
|
260
|
+
envDoctor
|
|
261
|
+
};
|
|
262
|
+
//# sourceMappingURL=chunk-L5DK6LRX.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/envDoctor.ts","../src/errors/EnvDoctorError.ts","../src/validators/primitives.ts","../src/core/runner.ts"],"sourcesContent":["import * as dotenv from \"dotenv\";\nimport { validateAndParse } from \"./runner\";\nimport type { EnvDoctorOptions, EnvDoctorResult, EnvDoctorSchema } from \"../types\";\n\nexport type { EnvDoctorOptions, EnvDoctorSchema } from \"../types\";\nexport { EnvDoctorError } from \"../errors/EnvDoctorError\";\n\nexport function envDoctor<TSchema extends EnvDoctorSchema>(\n schema: TSchema,\n options: EnvDoctorOptions = {}\n): EnvDoctorResult<TSchema> {\n const { loadDotEnv = true, env = process.env } = options;\n\n if (loadDotEnv) dotenv.config();\n\n return validateAndParse(schema, env);\n}\n","export type EnvDoctorIssue =\n | { key: string; kind: \"missing\"; message: string }\n | { key: string; kind: \"invalid\"; message: string };\n\nexport class EnvDoctorError extends Error {\n public readonly issues: EnvDoctorIssue[];\n\n constructor(issues: EnvDoctorIssue[]) {\n const header = \"ENV validation failed\";\n const lines = issues.map((i) => `- ${i.key}: ${i.message}`);\n super([header, ...lines].join(\"\\n\"));\n this.name = \"EnvDoctorError\";\n this.issues = issues;\n }\n}\n","import type { EnvBaseType, EnvSchemaValue } from \"../types\";\n\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\nexport function parseByType(type: EnvBaseType, raw: string): unknown {\n switch (type) {\n case \"string\":\n return raw;\n\n case \"number\": {\n const n = Number(raw.trim());\n if (!Number.isFinite(n)) throw new Error(`expected number, got \"${raw}\"`);\n return n;\n }\n\n case \"boolean\": {\n const v = raw.trim().toLowerCase();\n if ([\"true\", \"1\", \"yes\", \"y\", \"on\"].includes(v)) return true;\n if ([\"false\", \"0\", \"no\", \"n\", \"off\"].includes(v)) return false;\n throw new Error(\n `expected boolean (true/false/1/0/yes/no/on/off), got \"${raw}\"`,\n );\n }\n\n case \"json\": {\n try {\n return JSON.parse(raw);\n } catch {\n throw new Error(`expected json, got \"${raw}\"`);\n }\n }\n\n case \"url\": {\n try {\n new URL(raw);\n return raw;\n } catch {\n throw new Error(`expected url, got \"${raw}\"`);\n }\n }\n\n case \"email\": {\n const s = raw.trim();\n if (!EMAIL_RE.test(s)) {\n throw new Error(`expected email, got \"${raw}\"`);\n }\n return s;\n }\n\n default: {\n throw new Error(`unsupported primitive type: ${String(type)}`);\n }\n }\n}\n\nfunction hasOwn(obj: unknown, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(obj, key);\n}\n\n/**\n * Type-check + normalize a default value into the right runtime type.\n * - Allows string defaults for number/boolean (parsed)\n * - Validates url/email defaults\n * - json defaults can be any value\n */\nfunction coerceDefault(kind: EnvBaseType, def: unknown): unknown {\n if (def === undefined) return undefined;\n\n switch (kind) {\n case \"string\": {\n if (typeof def !== \"string\")\n throw new Error(`default for string must be a string`);\n return def;\n }\n\n case \"number\": {\n if (typeof def === \"number\") {\n if (!Number.isFinite(def))\n throw new Error(`default for number must be finite`);\n return def;\n }\n if (typeof def === \"string\") return parseByType(\"number\", def);\n throw new Error(`default for number must be number or string`);\n }\n\n case \"boolean\": {\n if (typeof def === \"boolean\") return def;\n if (typeof def === \"string\") return parseByType(\"boolean\", def);\n throw new Error(`default for boolean must be boolean or string`);\n }\n\n case \"json\": {\n // allow any JSON-ish value\n return def;\n }\n\n case \"url\": {\n if (typeof def !== \"string\")\n throw new Error(`default for url must be a string`);\n return parseByType(\"url\", def);\n }\n\n case \"email\": {\n if (typeof def !== \"string\")\n throw new Error(`default for email must be a string`);\n return parseByType(\"email\", def);\n }\n\n /* c8 ignore next */\n default: {\n throw new Error(`unsupported default kind: ${String(kind)}`);\n }\n }\n}\n\n/** Normalized spec used by the runner */\nexport type NormalizedSpec =\n | { kind: EnvBaseType; optional: boolean; defaultValue?: unknown }\n | {\n kind: \"enum\";\n optional: boolean;\n values: readonly string[];\n defaultValue?: string;\n }\n | {\n kind: \"regex\";\n optional: boolean;\n re: RegExp;\n display: string;\n defaultValue?: string;\n };\n\nexport function normalizeSpec(schemaValue: EnvSchemaValue): NormalizedSpec {\n // --------------------\n // String style: \"number?\" etc.\n // --------------------\n if (typeof schemaValue === \"string\") {\n const optional = schemaValue.endsWith(\"?\");\n const base = optional ? schemaValue.slice(0, -1) : schemaValue;\n\n const allowed: readonly EnvBaseType[] = [\n \"string\",\n \"number\",\n \"boolean\",\n \"json\",\n \"url\",\n \"email\",\n ];\n\n if (!allowed.includes(base as EnvBaseType)) {\n throw new Error(\n `Unsupported type \"${schemaValue}\". Supported: string, number, boolean, json, url, email (optional with ?)`,\n );\n }\n\n return { kind: base as EnvBaseType, optional };\n }\n\n // --------------------\n // Object style\n // --------------------\n if (\n !schemaValue ||\n typeof schemaValue !== \"object\" ||\n Array.isArray(schemaValue)\n ) {\n throw new Error(\"Schema value must be a string or object spec.\");\n }\n\n const t = (schemaValue as any).type;\n\n // --------------------\n // Primitive object spec: { type: \"number\", optional?: true, default?: ... }\n // --------------------\n const primitiveAllowed: readonly EnvBaseType[] = [\n \"string\",\n \"number\",\n \"boolean\",\n \"json\",\n \"url\",\n \"email\",\n ];\n\n if (primitiveAllowed.includes(t)) {\n const optional = !!(schemaValue as any).optional;\n const defaultValue = hasOwn(schemaValue, \"default\")\n ? coerceDefault(t as EnvBaseType, (schemaValue as any).default)\n : undefined;\n\n return { kind: t as EnvBaseType, optional, defaultValue };\n }\n\n // --------------------\n // Enum: { type: \"enum\", values: [...], optional?: true, default?: \"dev\" }\n // --------------------\n if (t === \"enum\") {\n const values = (schemaValue as any).values;\n if (\n !Array.isArray(values) ||\n values.length === 0 ||\n !values.every((v: any) => typeof v === \"string\")\n ) {\n throw new Error(`enum spec requires \"values\": string[] (non-empty)`);\n }\n\n const optional = !!(schemaValue as any).optional;\n\n let defaultValue: string | undefined = undefined;\n if (hasOwn(schemaValue, \"default\")) {\n const def = (schemaValue as any).default;\n if (typeof def !== \"string\") {\n throw new Error(`default for enum must be a string`);\n }\n if (!values.includes(def)) {\n throw new Error(\n `default \"${def}\" must be one of [${values.join(\", \")}]`,\n );\n }\n defaultValue = def;\n }\n\n return { kind: \"enum\", optional, values, defaultValue };\n }\n\n // --------------------\n // Regex: { type: \"regex\", pattern: \"...\", flags?: \"...\", optional?: true, default?: \"abc\" }\n // --------------------\n if (t === \"regex\") {\n const pattern = (schemaValue as any).pattern;\n const flags = (schemaValue as any).flags;\n\n if (typeof pattern !== \"string\" || pattern.length === 0) {\n throw new Error(`regex spec requires \"pattern\": string`);\n }\n if (flags !== undefined && typeof flags !== \"string\") {\n throw new Error(`regex spec \"flags\" must be a string if provided`);\n }\n\n let re: RegExp;\n try {\n re = new RegExp(pattern, flags);\n } catch (e) {\n throw new Error(`invalid regex: ${String(e)}`);\n }\n\n const display = `/${pattern}/${flags ?? \"\"}`;\n const optional = !!(schemaValue as any).optional;\n\n let defaultValue: string | undefined = undefined;\n if (hasOwn(schemaValue, \"default\")) {\n const def = (schemaValue as any).default;\n if (typeof def !== \"string\") {\n throw new Error(`default for regex must be a string`);\n }\n if (!re.test(def)) {\n throw new Error(`default \"${def}\" does not match ${display}`);\n }\n defaultValue = def;\n }\n\n return { kind: \"regex\", optional, re, display, defaultValue };\n }\n\n throw new Error(\n `Unsupported object spec type \"${String(t)}\". Supported: primitives (string/number/boolean/json/url/email), enum, regex`,\n );\n}\n","import { EnvDoctorError, type EnvDoctorIssue } from \"../errors/EnvDoctorError\";\nimport { normalizeSpec, parseByType } from \"../validators/primitives\";\nimport type { EnvDoctorResult, EnvDoctorSchema } from \"../types\";\n\nexport function validateAndParse<TSchema extends EnvDoctorSchema>(\n schema: TSchema,\n env: Record<string, string | undefined>,\n): EnvDoctorResult<TSchema> {\n const issues: EnvDoctorIssue[] = [];\n const out: Record<string, unknown> = {};\n\n for (const [key, schemaValue] of Object.entries(schema)) {\n let spec: ReturnType<typeof normalizeSpec>;\n\n try {\n spec = normalizeSpec(schemaValue);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n issues.push({ key, kind: \"invalid\", message: msg });\n continue;\n }\n\n const raw = env[key];\n\n // treat undefined or empty string as \"missing\"\n if (raw === undefined || raw === \"\") {\n if (spec.defaultValue !== undefined) {\n out[key] = spec.defaultValue;\n } else if (spec.optional) {\n out[key] = undefined;\n } else {\n issues.push({\n key,\n kind: \"missing\",\n message: \"missing required environment variable\",\n });\n }\n continue;\n }\n\n try {\n if (spec.kind === \"enum\") {\n if (!spec.values.includes(raw)) {\n throw new Error(\n `expected one of [${spec.values.join(\", \")}], got \"${raw}\"`,\n );\n }\n out[key] = raw;\n } else if (spec.kind === \"regex\") {\n if (!spec.re.test(raw)) {\n throw new Error(`does not match ${spec.display}`);\n }\n out[key] = raw;\n } else {\n // primitives: string/number/boolean/json/url/email\n out[key] = parseByType(spec.kind, raw);\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n issues.push({ key, kind: \"invalid\", message: msg });\n }\n }\n\n if (issues.length > 0) throw new EnvDoctorError(issues);\n\n return out as EnvDoctorResult<TSchema>;\n}\n"],"mappings":"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","names":[]}
|