env-typed-checker 0.2.1 → 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,305 +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
-
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)
29
-
30
- ---
31
-
32
- ## 📦 Install
20
+ ## Install
33
21
 
34
22
  ```bash
35
23
  npm install env-typed-checker
36
24
  ```
37
25
 
38
- Or run via npx:
26
+ Or run directly:
39
27
 
40
28
  ```bash
41
29
  npx env-typed-checker --help
42
30
  ```
43
31
 
44
- ## 🚀 Quick Start (Code)
32
+ Requirements:
45
33
 
46
- ```ts
47
- import { envDoctor } from "env-typed-checker";
34
+ - Node.js `>=18`
48
35
 
49
- export const config = envDoctor({
50
- PORT: "number",
51
- DB_URL: "url",
52
- ADMIN_EMAIL: "email",
53
- DEBUG: "boolean?",
54
- });
36
+ ## Quick start (recommended): one schema for CLI + code
55
37
 
56
- ```
38
+ If you are new to this package, start with this pattern.
57
39
 
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)
40
+ ### 1) Create `env.schema.json`
67
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
+ ```
68
53
 
69
- ### 🧩 Supported Types
70
- | Type | Description |
71
- | :--- | :--- |
72
- | **string** | Any string value |
73
- | **number** | A finite number (automatically parsed from string) |
74
- | **boolean** | Supports `true` / `false`, `1` / `0`, and `yes` / `no` |
75
- | **json** | Validates and parses a valid JSON string |
76
- | **url** | Validates for a properly formatted URL |
77
- | **email** | Validates for a properly formatted email |
54
+ ### 2) Reuse the same schema in app code
78
55
 
56
+ ESM (NodeNext / `"type": "module"`):
79
57
 
80
- ### Optional Values
81
- Add ? to make a variable optional:
82
58
  ```ts
83
- envDoctor({ DEBUG: "boolean?" });
84
- ```
59
+ // src/config.ts
60
+ import { envDoctor, type EnvDoctorSchema } from "env-typed-checker";
61
+ import schema from "../env.schema.json" assert { type: "json" };
85
62
 
86
- Or use object-style:
87
-
88
- ```ts
89
- envDoctor({
90
- DEBUG: { type: "boolean", optional: true },
63
+ export const config = envDoctor(schema as EnvDoctorSchema, {
64
+ loadDotEnv: true,
91
65
  });
92
66
  ```
93
- Missing optional vars become `undefined` unless you provide a default.
94
67
 
95
- ### 🎯 Defaults
96
- Defaults can be provided in object-style specs.
68
+ CommonJS (or TS compiling to CJS):
97
69
 
98
70
  ```ts
99
- import { envDoctor } from "env-typed-checker";
71
+ // src/config.ts
72
+ import { envDoctor, type EnvDoctorSchema } from "env-typed-checker";
100
73
 
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
- ```
74
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
75
+ const schema = require("../env.schema.json") as EnvDoctorSchema;
107
76
 
108
- ### Notes:
77
+ export const config = envDoctor(schema, { loadDotEnv: true });
78
+ ```
109
79
 
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
80
+ TypeScript JSON import note:
114
81
 
115
- ### ✅ Enum and Regex
82
+ ```json
83
+ {
84
+ "compilerOptions": {
85
+ "resolveJsonModule": true,
86
+ "esModuleInterop": true
87
+ }
88
+ }
89
+ ```
116
90
 
117
- ### Enum
91
+ ### 3) Validate env via CLI
118
92
 
119
- ```ts
120
- const config = envDoctor({
121
- NODE_ENV: { type: "enum", values: ["dev", "prod"] },
122
- });
93
+ ```bash
94
+ npx env-typed-checker check --schema env.schema.json
123
95
  ```
124
96
 
125
- ### Regex
97
+ ### 4) Generate `.env.example` from the same schema
126
98
 
127
- ```ts
128
- const config = envDoctor({
129
- SLUG: { type: "regex", pattern: "^[a-z0-9-]+$", flags: "i" },
130
- });
99
+ ```bash
100
+ npx env-typed-checker generate --schema env.schema.json --out .env.example --comment-docs --example-values
131
101
  ```
132
102
 
133
- ### ⚙️ Options
103
+ Example output:
134
104
 
135
- ```ts
136
- envDoctor(schema, {
137
- loadDotEnv: true, // default: true (loads .env)
138
- env: process.env // default: process.env (override for tests)
139
- });
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
140
113
  ```
141
114
 
115
+ ### 5) Use in CI (GitHub Actions annotations)
142
116
 
143
- ### 🧪 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
144
123
 
145
124
  ```ts
146
125
  import { envDoctor } from "env-typed-checker";
147
126
 
148
- const cfg = envDoctor(
149
- { PORT: "number" },
150
- { 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
+ },
151
137
  );
152
-
153
- console.log(cfg.PORT); // 3000
154
138
  ```
155
139
 
156
- ### Error Example
140
+ ### Parsed output types
157
141
 
158
- 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` |
159
152
 
160
- ```env
161
- PORT=abc
162
- DB_URL=not-a-url
163
- NODE_ENV=staging
164
- ```
153
+ ## Schema reference
165
154
 
166
- ```ts
167
- import { envDoctor } from "env-typed-checker";
155
+ ### Primitive shorthand
168
156
 
169
- envDoctor({
170
- PORT: "number",
171
- DB_URL: "url"
172
- NODE_ENV: { type: "enum", values: ["dev", "prod"] },
173
- });
157
+ ```json
158
+ {
159
+ "PORT": "number",
160
+ "DEBUG": "boolean?",
161
+ "DB_URL": "url"
162
+ }
174
163
  ```
175
164
 
176
- ### Output:
165
+ - Add `?` to mark optional keys.
166
+ - Missing optional keys become `undefined` (unless default exists).
177
167
 
178
- ```ts
179
- ENV validation failed
180
- - PORT: expected number, got "abc"
181
- - DB_URL: expected url, got "not-a-url"
182
- - 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
+ }
183
177
  ```
184
- All errors are shown together so you can fix them in one go.
185
178
 
186
- # 🖥️ CLI
179
+ ### Defaults
187
180
 
188
- 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
189
185
 
190
- ## 1) Create a schema file
186
+ ### Metadata fields (object syntax only)
191
187
 
192
- `env.schema.json`
193
188
  ```json
194
189
  {
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" }
190
+ "API_KEY": {
191
+ "type": "string",
192
+ "description": "External service token",
193
+ "example": "sk_test_123",
194
+ "secret": true
195
+ }
201
196
  }
202
197
  ```
203
198
 
204
- ## 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`
205
202
 
206
- ```bash
207
- 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
+ }
208
209
  ```
209
210
 
210
- Useful flags
211
+ ### Runtime behavior notes
211
212
 
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
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`
215
220
 
216
- # Skip dotenv loading and validate only process.env
217
- npx env-typed-checker check --schema env.schema.json --no-dotenv
221
+ ```bash
222
+ env-typed-checker check --schema <file> [--env-file <file>] [--no-dotenv] [--format pretty|json|github]
218
223
  ```
219
224
 
220
- ### `generate` — generate or update `.env`
225
+ Flags:
221
226
 
222
- Generate values from schema (writes missing keys; does not overwrite existing values).
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`)
223
231
 
224
- ```bash
225
- npx env-typed-checker generate --schema env.schema.json
232
+ ### `check` output formats
233
+
234
+ `pretty` (default):
235
+
236
+ ```text
237
+ ENV validation failed
238
+ - PORT: expected number, got "abc"
226
239
  ```
227
- ### By default:
228
240
 
229
- - output file: .env
230
- - mode: update
241
+ `json`:
242
+
243
+ ```json
244
+ {"error":"ENV validation failed","issues":[{"key":"PORT","kind":"invalid","message":"expected number, got \"abc\""}]}
245
+ ```
231
246
 
232
- ### Flags
247
+ `github` (for workflow annotations):
233
248
 
234
- ```bash
235
- # Custom output file
236
- npx env-typed-checker generate --schema env.schema.json --out .env.example
249
+ ```text
250
+ ::error title=ENV validation::PORT: expected number, got "abc"
251
+ ```
237
252
 
238
- # Create mode: refuse to overwrite an existing file
239
- npx env-typed-checker generate --schema env.schema.json --out .env --mode=create
253
+ When a key has `secret: true`, CLI output redacts raw invalid values:
240
254
 
241
- # Update mode: append only missing keys (safe)
242
- npx env-typed-checker generate --schema env.schema.json --out .env --mode=update
255
+ ```text
256
+ SECRET_TOKEN: expected number, got "<redacted>"
257
+ ```
243
258
 
244
- # Do not write defaults (leave blank values)
245
- npx env-typed-checker generate --schema env.schema.json --no-defaults
259
+ ### `generate`
246
260
 
247
- # Add inline type comments (useful for .env.example)
248
- npx env-typed-checker generate --schema env.schema.json --comment-types
261
+ ```bash
262
+ env-typed-checker generate --schema <file> [--out <file>] [--mode update|create] [--no-defaults] [--comment-types] [--comment-docs] [--example-values]
249
263
  ```
250
264
 
251
- ### Exit codes
265
+ Defaults:
266
+
267
+ - output file: `.env`
268
+ - mode: `update`
269
+ - behavior: append only missing keys
270
+
271
+ Flags:
252
272
 
253
- * `0` = OK
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
254
280
 
255
- * `1` = validation failed
281
+ ## Exit codes
256
282
 
257
- * `2` = CLI usage / unexpected error
283
+ - `0` success
284
+ - `1` validation failed
285
+ - `2` CLI usage error or unexpected error
258
286
 
259
- ## CI Example (GitHub Actions)
287
+ ## CI examples
260
288
 
261
- Add this to your workflow to fail the build if env is invalid:
289
+ GitHub Actions with annotations:
262
290
 
263
291
  ```yml
264
292
  - name: Validate env
265
- run: npx env-typed-checker check --schema env.schema.json
293
+ run: npx env-typed-checker check --schema env.schema.json --format github
266
294
  ```
267
- If you use a specific env file in CI:
295
+
296
+ With custom env file:
268
297
 
269
298
  ```yml
270
299
  - name: Validate env
271
- 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
272
301
  ```
273
302
 
274
- ## 🛠 Development
275
- Clone the repo and install:
276
- ```bash
277
- npm install
278
- npm test
279
- ```
280
- ### Common scripts:
281
- ```bash
282
- npm run build # build package
283
- npm run test # run tests
284
- npm run typecheck # TypeScript check
285
- npm run dev # watch build
286
- ```
303
+ ## Testing with custom env in code
287
304
 
288
- ### 🤝 Contributing
289
- PRs are welcome!
305
+ ```ts
306
+ import { envDoctor } from "env-typed-checker";
290
307
 
291
- * Improve docs / examples
292
- * Add more schema features
293
- * Improve CLI output formatting
294
- * Add integrations / templates
308
+ const cfg = envDoctor(
309
+ { PORT: "number" },
310
+ { loadDotEnv: false, env: { PORT: "3000" } },
311
+ );
295
312
 
313
+ console.log(cfg.PORT); // 3000
314
+ ```
296
315
 
297
- # 📝 License
298
- MIT
316
+ ## Common mistakes
299
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
300
321
 
301
- ---
322
+ ## Development
302
323
 
303
- ```yml
304
- ::contentReference[oaicite:0]{index=0}
305
- ```
324
+ ```bash
325
+ npm install
326
+ npm run lint
327
+ npm run typecheck
328
+ npm run test:coverage
329
+ npm run build
330
+ ```
331
+
332
+ ## License
333
+
334
+ MIT
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ "use strict";
2
3
 
3
- const { runCli } = require("../dist/cli.js");
4
+ const { runCli } = require("../dist/cli/index.js");
4
5
 
5
6
  const code = runCli(process.argv.slice(2), console);
6
- process.exitCode = code;
7
+ process.exitCode = typeof code === "number" ? code : 1;
@@ -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