env-typed-checker 0.2.3 → 0.2.4

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 CHANGED
@@ -1,299 +1,334 @@
1
- # env-typed-checker
1
+ # env-typed-checker
2
2
 
3
- Validate and parse environment variables using a tiny schema with both a **TypeScript/Node API** and a **CLI**.
3
+ Validate and parse environment variables with a small schema, using either:
4
4
 
5
- It helps your app fail fast when configuration is wrong:
5
+ - TypeScript/Node API (`envDoctor`)
6
+ - CLI (`env-typed-checker check` and `generate`)
6
7
 
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
8
+ This package helps your app fail fast when config is wrong.
9
+ This README is the complete guide for installing, configuring, and using the package.
13
10
 
11
+ ## Why use this
14
12
 
15
- ---
13
+ - Catch missing env vars early
14
+ - Parse types (`number`, `boolean`, `json`) safely
15
+ - Validate `url`, `email`, `enum`, and `regex` values
16
+ - Keep one schema for runtime + CI checks
17
+ - Generate `.env` / `.env.example` from schema
18
+ - Redact secret values in CLI validation output
16
19
 
17
- ## ✨ Features
18
-
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
- - `check` → validate env
25
- - `generate` → generate/update `.env` from schema (no overwrite by default)
26
- - Uses `.env` via `dotenv` (optional)
27
- - Friendly aggregated errors (see everything that’s wrong at once)
28
-
29
- ---
30
-
31
- ## 📦 Install
20
+ ## Install
32
21
 
33
22
  ```bash
34
23
  npm install env-typed-checker
35
24
  ```
36
25
 
37
- Or run via npx:
26
+ Or run directly:
38
27
 
39
28
  ```bash
40
29
  npx env-typed-checker --help
41
30
  ```
42
31
 
43
- ## 🚀 Quick Start (Code)
44
-
45
- ```ts
46
- import { envDoctor } from "env-typed-checker";
32
+ Requirements:
47
33
 
48
- export const config = envDoctor({
49
- PORT: "number",
50
- DB_URL: "url",
51
- ADMIN_EMAIL: "email",
52
- DEBUG: "boolean?",
53
- });
34
+ - Node.js `>=18`
54
35
 
55
- ```
36
+ ## Quick start (recommended): one schema for CLI + code
56
37
 
57
- ### What you get
38
+ If you are new to this package, start with this pattern.
58
39
 
59
- * PORT `number`
60
- * DB_URL → `string` (validated as URL)
61
- * ADMIN_EMAIL → `string` (validated as email)
62
- * DEBUG → `boolean` | `undefined` (optional)
40
+ ### 1) Create `env.schema.json`
63
41
 
42
+ ```json
43
+ {
44
+ "PORT": { "type": "number", "default": 3000, "description": "HTTP port", "example": "8080" },
45
+ "DB_URL": { "type": "url", "description": "Primary database URL", "example": "postgres://user:pass@localhost:5432/app", "secret": true },
46
+ "ADMIN_EMAIL": { "type": "email", "description": "Ops contact email", "example": "ops@example.com" },
47
+ "DEBUG": "boolean?",
48
+ "NODE_ENV": { "type": "enum", "values": ["dev", "prod"], "default": "dev" },
49
+ "SLUG": { "type": "regex", "pattern": "^[a-z0-9-]+$", "flags": "i" },
50
+ "FEATURE_FLAGS": { "type": "json", "default": { "beta": false } }
51
+ }
52
+ ```
64
53
 
65
- ### 🧩 Supported Types
66
- | Type | Description |
67
- | :--- | :--- |
68
- | **string** | Any string value |
69
- | **number** | A finite number (automatically parsed from string) |
70
- | **boolean** | Supports `true` / `false`, `1` / `0`, and `yes` / `no` |
71
- | **json** | Validates and parses a valid JSON string |
72
- | **url** | Validates for a properly formatted URL |
73
- | **email** | Validates for a properly formatted email |
54
+ ### 2) Reuse the same schema in app code
74
55
 
56
+ ESM (NodeNext / `"type": "module"`):
75
57
 
76
- ### Optional Values
77
- Add ? to make a variable optional:
78
58
  ```ts
79
- envDoctor({ DEBUG: "boolean?" });
80
- ```
59
+ // src/config.ts
60
+ import { envDoctor, type EnvDoctorSchema } from "env-typed-checker";
61
+ import schema from "../env.schema.json" assert { type: "json" };
81
62
 
82
- Or use object-style:
83
-
84
- ```ts
85
- envDoctor({
86
- DEBUG: { type: "boolean", optional: true },
63
+ export const config = envDoctor(schema as EnvDoctorSchema, {
64
+ loadDotEnv: true,
87
65
  });
88
66
  ```
89
- Missing optional vars become `undefined` unless you provide a default.
90
67
 
91
- ### 🎯 Defaults
92
- Defaults can be provided in object-style specs.
68
+ CommonJS (or TS compiling to CJS):
93
69
 
94
70
  ```ts
95
- import { envDoctor } from "env-typed-checker";
71
+ // src/config.ts
72
+ import { envDoctor, type EnvDoctorSchema } from "env-typed-checker";
96
73
 
97
- const config = envDoctor({
98
- PORT: { type: "number", default: 3000 },
99
- DEBUG: { type: "boolean", default: "false" }, // string is allowed (parsed)
100
- NODE_ENV: { type: "enum", values: ["dev", "prod"], default: "dev" },
101
- });
102
- ```
74
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
75
+ const schema = require("../env.schema.json") as EnvDoctorSchema;
103
76
 
104
- ### Notes:
77
+ export const config = envDoctor(schema, { loadDotEnv: true });
78
+ ```
105
79
 
106
- - `number` default: number or string (string will be parsed)
107
- - `boolean` default: boolean or string (string will be parsed)
108
- - `url/email` defaults must be strings and must validate
109
- - `json` defaults can be any JSON-like value
80
+ TypeScript JSON import note:
110
81
 
111
- ### ✅ Enum and Regex
82
+ ```json
83
+ {
84
+ "compilerOptions": {
85
+ "resolveJsonModule": true,
86
+ "esModuleInterop": true
87
+ }
88
+ }
89
+ ```
112
90
 
113
- ### Enum
91
+ ### 3) Validate env via CLI
114
92
 
115
- ```ts
116
- const config = envDoctor({
117
- NODE_ENV: { type: "enum", values: ["dev", "prod"] },
118
- });
93
+ ```bash
94
+ npx env-typed-checker check --schema env.schema.json
119
95
  ```
120
96
 
121
- ### Regex
97
+ ### 4) Generate `.env.example` from the same schema
122
98
 
123
- ```ts
124
- const config = envDoctor({
125
- SLUG: { type: "regex", pattern: "^[a-z0-9-]+$", flags: "i" },
126
- });
99
+ ```bash
100
+ npx env-typed-checker generate --schema env.schema.json --out .env.example --comment-docs --example-values
127
101
  ```
128
102
 
129
- ### ⚙️ Options
103
+ Example output:
130
104
 
131
- ```ts
132
- envDoctor(schema, {
133
- loadDotEnv: true, // default: true (loads .env)
134
- env: process.env // default: process.env (override for tests)
135
- });
105
+ ```env
106
+ # HTTP port
107
+ # example: 8080
108
+ PORT=8080
109
+
110
+ # Primary database URL
111
+ # example: postgres://user:pass@localhost:5432/app
112
+ DB_URL=postgres://user:pass@localhost:5432/app
136
113
  ```
137
114
 
115
+ ### 5) Use in CI (GitHub Actions annotations)
138
116
 
139
- ### 🧪 Testing with custom env
117
+ ```yml
118
+ - name: Validate env
119
+ run: npx env-typed-checker check --schema env.schema.json --format github
120
+ ```
121
+
122
+ ## API quick reference
140
123
 
141
124
  ```ts
142
125
  import { envDoctor } from "env-typed-checker";
143
126
 
144
- const cfg = envDoctor(
145
- { PORT: "number" },
146
- { loadDotEnv: false, env: { PORT: "3000" } }
127
+ const config = envDoctor(
128
+ {
129
+ PORT: "number",
130
+ DEBUG: "boolean?",
131
+ NODE_ENV: { type: "enum", values: ["dev", "prod"] },
132
+ },
133
+ {
134
+ loadDotEnv: true,
135
+ env: process.env,
136
+ },
147
137
  );
148
-
149
- console.log(cfg.PORT); // 3000
150
138
  ```
151
139
 
152
- ### Error Example
140
+ ### Parsed output types
153
141
 
154
- Given a `.env` like:
142
+ | Schema type | Parsed result |
143
+ | :--- | :--- |
144
+ | `string` | `string` |
145
+ | `number` | `number` |
146
+ | `boolean` | `boolean` |
147
+ | `json` | `unknown` |
148
+ | `url` | `string` |
149
+ | `email` | `string` |
150
+ | `enum` | one of listed values |
151
+ | `regex` | `string` |
155
152
 
156
- ```env
157
- PORT=abc
158
- DB_URL=not-a-url
159
- NODE_ENV=staging
160
- ```
153
+ ## Schema reference
161
154
 
162
- ```ts
163
- import { envDoctor } from "env-typed-checker";
155
+ ### Primitive shorthand
164
156
 
165
- envDoctor({
166
- PORT: "number",
167
- DB_URL: "url"
168
- NODE_ENV: { type: "enum", values: ["dev", "prod"] },
169
- });
157
+ ```json
158
+ {
159
+ "PORT": "number",
160
+ "DEBUG": "boolean?",
161
+ "DB_URL": "url"
162
+ }
170
163
  ```
171
164
 
172
- ### Output:
165
+ - Add `?` to mark optional keys.
166
+ - Missing optional keys become `undefined` (unless default exists).
173
167
 
174
- ```ts
175
- ENV validation failed
176
- - PORT: expected number, got "abc"
177
- - DB_URL: expected url, got "not-a-url"
178
- - NODE_ENV: must be one of [dev, prod]
168
+ ### Object syntax
169
+
170
+ ```json
171
+ {
172
+ "PORT": { "type": "number", "default": 3000 },
173
+ "DEBUG": { "type": "boolean", "optional": true },
174
+ "NODE_ENV": { "type": "enum", "values": ["dev", "prod"] },
175
+ "SLUG": { "type": "regex", "pattern": "^[a-z0-9-]+$", "flags": "i" }
176
+ }
179
177
  ```
180
- All errors are shown together so you can fix them in one go.
181
178
 
182
- # 🖥️ CLI
179
+ ### Defaults
183
180
 
184
- Validate your environment without writing code — perfect for CI pipelines.
181
+ - `number`: number or numeric string
182
+ - `boolean`: boolean or boolean-like string
183
+ - `url` / `email`: valid strings
184
+ - `json`: any JSON-like value
185
185
 
186
- ## 1) Create a schema file
186
+ ### Metadata fields (object syntax only)
187
187
 
188
- `env.schema.json`
189
188
  ```json
190
189
  {
191
- "PORT": "number",
192
- "DB_URL": "url",
193
- "ADMIN_EMAIL": "email",
194
- "DEBUG": "boolean?",
195
- "NODE_ENV": { "type": "enum", "values": ["dev", "prod"] },
196
- "SLUG": { "type": "regex", "pattern": "^[a-z0-9-]+$", "flags": "i" }
190
+ "API_KEY": {
191
+ "type": "string",
192
+ "description": "External service token",
193
+ "example": "sk_test_123",
194
+ "secret": true
195
+ }
197
196
  }
198
197
  ```
199
198
 
200
- ## 2) Run the check
199
+ - `description?: string` used by `generate --comment-docs`
200
+ - `example?: string` used in comments and optional generation values
201
+ - `secret?: boolean` redacts invalid raw values in CLI `check`
201
202
 
202
- ```bash
203
- npx env-typed-checker check --schema env.schema.json
203
+ If you need metadata for a primitive shorthand key, wrap it in object syntax:
204
+
205
+ ```json
206
+ {
207
+ "PORT": { "type": "number", "description": "HTTP port" }
208
+ }
204
209
  ```
205
210
 
206
- Useful flags
211
+ ### Runtime behavior notes
212
+
213
+ - Empty strings (`""`) are treated as missing values.
214
+ - Validation errors are aggregated and returned together.
215
+ - CLI `check` merges `.env` values over current shell env when dotenv loading is enabled.
216
+
217
+ ## CLI reference
218
+
219
+ ### `check`
207
220
 
208
221
  ```bash
209
- # Use a specific env file (instead of .env)
210
- npx env-typed-checker check --schema env.schema.json --env-file .env.production
222
+ env-typed-checker check --schema <file> [--env-file <file>] [--no-dotenv] [--format pretty|json|github]
223
+ ```
211
224
 
212
- # Skip dotenv loading and validate only process.env
213
- npx env-typed-checker check --schema env.schema.json --no-dotenv
225
+ Flags:
226
+
227
+ - `--schema <file>` required schema file
228
+ - `--env-file <file>` env file path (default `.env`)
229
+ - `--no-dotenv` validate only current `process.env`
230
+ - `--format pretty|json|github` output style (default `pretty`)
231
+
232
+ ### `check` output formats
233
+
234
+ `pretty` (default):
235
+
236
+ ```text
237
+ ENV validation failed
238
+ - PORT: expected number, got "abc"
214
239
  ```
215
240
 
216
- ### `generate` — generate or update `.env`
241
+ `json`:
242
+
243
+ ```json
244
+ {"error":"ENV validation failed","issues":[{"key":"PORT","kind":"invalid","message":"expected number, got \"abc\""}]}
245
+ ```
217
246
 
218
- Generate values from schema (writes missing keys; does not overwrite existing values).
247
+ `github` (for workflow annotations):
219
248
 
220
- ```bash
221
- npx env-typed-checker generate --schema env.schema.json
249
+ ```text
250
+ ::error title=ENV validation::PORT: expected number, got "abc"
222
251
  ```
223
- ### By default:
224
252
 
225
- - output file: .env
226
- - mode: update
253
+ When a key has `secret: true`, CLI output redacts raw invalid values:
227
254
 
228
- ### Flags
255
+ ```text
256
+ SECRET_TOKEN: expected number, got "<redacted>"
257
+ ```
258
+
259
+ ### `generate`
229
260
 
230
261
  ```bash
231
- # Custom output file
232
- npx env-typed-checker generate --schema env.schema.json --out .env.example
262
+ env-typed-checker generate --schema <file> [--out <file>] [--mode update|create] [--no-defaults] [--comment-types] [--comment-docs] [--example-values]
263
+ ```
233
264
 
234
- # Create mode: refuse to overwrite an existing file
235
- npx env-typed-checker generate --schema env.schema.json --out .env --mode=create
265
+ Defaults:
236
266
 
237
- # Update mode: append only missing keys (safe)
238
- npx env-typed-checker generate --schema env.schema.json --out .env --mode=update
267
+ - output file: `.env`
268
+ - mode: `update`
269
+ - behavior: append only missing keys
239
270
 
240
- # Do not write defaults (leave blank values)
241
- npx env-typed-checker generate --schema env.schema.json --no-defaults
271
+ Flags:
242
272
 
243
- # Add inline type comments (useful for .env.example)
244
- npx env-typed-checker generate --schema env.schema.json --comment-types
245
- ```
273
+ - `--out <file>` custom output path
274
+ - `--mode create` fail if file already exists
275
+ - `--mode update` append missing keys only
276
+ - `--no-defaults` write empty values instead of defaults
277
+ - `--comment-types` add inline type comments
278
+ - `--comment-docs` add `description` and `example` comments above keys
279
+ - `--example-values` use schema `example` values as generated values
246
280
 
247
- ### Exit codes
281
+ ## Exit codes
248
282
 
249
- * `0` = OK
250
- * `1` = validation failed
251
- * `2` = CLI usage / unexpected error
283
+ - `0` success
284
+ - `1` validation failed
285
+ - `2` CLI usage error or unexpected error
252
286
 
253
- ## CI Example (GitHub Actions)
287
+ ## CI examples
254
288
 
255
- Add this to your workflow to fail the build if env is invalid:
289
+ GitHub Actions with annotations:
256
290
 
257
291
  ```yml
258
292
  - name: Validate env
259
- run: npx env-typed-checker check --schema env.schema.json
293
+ run: npx env-typed-checker check --schema env.schema.json --format github
260
294
  ```
261
- If you use a specific env file in CI:
295
+
296
+ With custom env file:
262
297
 
263
298
  ```yml
264
299
  - name: Validate env
265
- run: npx env-typed-checker check --schema env.schema.json --env-file .env.ci
300
+ run: npx env-typed-checker check --schema env.schema.json --env-file .env.ci --format github
266
301
  ```
267
302
 
268
- ## 🛠 Development
269
- Clone the repo and install:
270
- ```bash
271
- npm install
272
- npm test
273
- ```
274
- ### Common scripts:
275
- ```bash
276
- npm run build # build package
277
- npm run test # run tests
278
- npm run typecheck # TypeScript check
279
- npm run dev # watch build
280
- ```
303
+ ## Testing with custom env in code
304
+
305
+ ```ts
306
+ import { envDoctor } from "env-typed-checker";
281
307
 
282
- ### 🤝 Contributing
283
- PRs are welcome!
308
+ const cfg = envDoctor(
309
+ { PORT: "number" },
310
+ { loadDotEnv: false, env: { PORT: "3000" } },
311
+ );
312
+
313
+ console.log(cfg.PORT); // 3000
314
+ ```
284
315
 
285
- * Improve docs / examples
286
- * Add more schema features
287
- * Improve CLI output formatting
288
- * Add integrations / templates
316
+ ## Common mistakes
289
317
 
318
+ - Missing `--schema` in CLI commands
319
+ - Using shorthand string syntax when you need metadata (`description`, `example`, `secret`)
320
+ - Forgetting `resolveJsonModule` when importing `env.schema.json` in TypeScript
290
321
 
291
- # 📝 License
292
- MIT
322
+ ## Development
293
323
 
324
+ ```bash
325
+ npm install
326
+ npm run lint
327
+ npm run typecheck
328
+ npm run test:coverage
329
+ npm run build
330
+ ```
294
331
 
295
- ---
332
+ ## License
296
333
 
297
- ```yml
298
- ::contentReference[oaicite:0]{index=0}
299
- ```
334
+ MIT
@@ -102,6 +102,33 @@ function coerceDefault(kind, def) {
102
102
  }
103
103
  }
104
104
  }
105
+ function normalizeMeta(schemaValue) {
106
+ let description = void 0;
107
+ if (hasOwn(schemaValue, "description")) {
108
+ const d = schemaValue.description;
109
+ if (d !== void 0 && typeof d !== "string") {
110
+ throw new Error(`"description" must be a string if provided`);
111
+ }
112
+ description = d;
113
+ }
114
+ let example = void 0;
115
+ if (hasOwn(schemaValue, "example")) {
116
+ const ex = schemaValue.example;
117
+ if (ex !== void 0 && typeof ex !== "string") {
118
+ throw new Error(`"example" must be a string if provided`);
119
+ }
120
+ example = ex;
121
+ }
122
+ let secret = false;
123
+ if (hasOwn(schemaValue, "secret")) {
124
+ const s = schemaValue.secret;
125
+ if (s !== void 0 && typeof s !== "boolean") {
126
+ throw new Error(`"secret" must be a boolean if provided`);
127
+ }
128
+ secret = s === true;
129
+ }
130
+ return { description, example, secret };
131
+ }
105
132
  function normalizeSpec(schemaValue) {
106
133
  if (typeof schemaValue === "string") {
107
134
  const optional = schemaValue.endsWith("?");
@@ -119,12 +146,17 @@ function normalizeSpec(schemaValue) {
119
146
  `Unsupported type "${schemaValue}". Supported: string, number, boolean, json, url, email (optional with ?)`
120
147
  );
121
148
  }
122
- return { kind: base, optional };
149
+ return {
150
+ kind: base,
151
+ optional,
152
+ secret: false
153
+ };
123
154
  }
124
155
  if (!schemaValue || typeof schemaValue !== "object" || Array.isArray(schemaValue)) {
125
156
  throw new Error("Schema value must be a string or object spec.");
126
157
  }
127
158
  const t = schemaValue.type;
159
+ const meta = normalizeMeta(schemaValue);
128
160
  const primitiveAllowed = [
129
161
  "string",
130
162
  "number",
@@ -136,7 +168,12 @@ function normalizeSpec(schemaValue) {
136
168
  if (primitiveAllowed.includes(t)) {
137
169
  const optional = !!schemaValue.optional;
138
170
  const defaultValue = hasOwn(schemaValue, "default") ? coerceDefault(t, schemaValue.default) : void 0;
139
- return { kind: t, optional, defaultValue };
171
+ return {
172
+ kind: t,
173
+ optional,
174
+ defaultValue,
175
+ ...meta
176
+ };
140
177
  }
141
178
  if (t === "enum") {
142
179
  const values = schemaValue.values;
@@ -157,7 +194,13 @@ function normalizeSpec(schemaValue) {
157
194
  }
158
195
  defaultValue = def;
159
196
  }
160
- return { kind: "enum", optional, values, defaultValue };
197
+ return {
198
+ kind: "enum",
199
+ optional,
200
+ values,
201
+ defaultValue,
202
+ ...meta
203
+ };
161
204
  }
162
205
  if (t === "regex") {
163
206
  const pattern = schemaValue.pattern;
@@ -187,7 +230,14 @@ function normalizeSpec(schemaValue) {
187
230
  }
188
231
  defaultValue = def;
189
232
  }
190
- return { kind: "regex", optional, re, display, defaultValue };
233
+ return {
234
+ kind: "regex",
235
+ optional,
236
+ re,
237
+ display,
238
+ defaultValue,
239
+ ...meta
240
+ };
191
241
  }
192
242
  throw new Error(
193
243
  `Unsupported object spec type "${String(t)}". Supported: primitives (string/number/boolean/json/url/email), enum, regex`
@@ -259,4 +309,4 @@ export {
259
309
  normalizeSpec,
260
310
  envDoctor
261
311
  };
262
- //# sourceMappingURL=chunk-L5DK6LRX.mjs.map
312
+ //# sourceMappingURL=chunk-CCJEPMPN.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 | {\n kind: EnvBaseType;\n optional: boolean;\n defaultValue?: unknown;\n description?: string;\n example?: string;\n secret: boolean;\n }\n | {\n kind: \"enum\";\n optional: boolean;\n values: readonly string[];\n defaultValue?: string;\n description?: string;\n example?: string;\n secret: boolean;\n }\n | {\n kind: \"regex\";\n optional: boolean;\n re: RegExp;\n display: string;\n defaultValue?: string;\n description?: string;\n example?: string;\n secret: boolean;\n };\n\nfunction normalizeMeta(schemaValue: Record<string, unknown>): {\n description?: string;\n example?: string;\n secret: boolean;\n} {\n let description: string | undefined = undefined;\n if (hasOwn(schemaValue, \"description\")) {\n const d = schemaValue.description;\n if (d !== undefined && typeof d !== \"string\") {\n throw new Error(`\"description\" must be a string if provided`);\n }\n description = d;\n }\n\n let example: string | undefined = undefined;\n if (hasOwn(schemaValue, \"example\")) {\n const ex = schemaValue.example;\n if (ex !== undefined && typeof ex !== \"string\") {\n throw new Error(`\"example\" must be a string if provided`);\n }\n example = ex;\n }\n\n let secret = false;\n if (hasOwn(schemaValue, \"secret\")) {\n const s = schemaValue.secret;\n if (s !== undefined && typeof s !== \"boolean\") {\n throw new Error(`\"secret\" must be a boolean if provided`);\n }\n secret = s === true;\n }\n\n return { description, example, secret };\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 {\n kind: base as EnvBaseType,\n optional,\n secret: false,\n };\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 const meta = normalizeMeta(schemaValue as Record<string, unknown>);\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 {\n kind: t as EnvBaseType,\n optional,\n defaultValue,\n ...meta,\n };\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 {\n kind: \"enum\",\n optional,\n values,\n defaultValue,\n ...meta,\n };\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 {\n kind: \"regex\",\n optional,\n re,\n display,\n defaultValue,\n ...meta,\n };\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":[]}