env-typed-checker 0.1.1 โ 0.2.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/README.md +124 -65
- package/bin/env-typed-checker.cjs +6 -0
- package/dist/chunk-U6GH2TRS.mjs +126 -0
- package/dist/chunk-U6GH2TRS.mjs.map +1 -0
- package/dist/cli.d.mts +7 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +247 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +94 -0
- package/dist/cli.mjs.map +1 -0
- 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,34 @@
|
|
|
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
6
|
|
|
7
|
-
- โ
|
|
8
|
-
- โ
|
|
9
|
-
- โ
|
|
10
|
-
- โ
|
|
7
|
+
- โ Missing required variables
|
|
8
|
+
- โ Wrong types (e.g. `PORT="abc"`)
|
|
9
|
+
- โ Invalid URLs or JSON
|
|
10
|
+
- โ Silent mistakes that only crash later
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
## โจ Features
|
|
15
15
|
|
|
16
|
-
- Simple schema syntax
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
- Zero dependencies except `dotenv`
|
|
16
|
+
- Simple schema syntax (`"number"`, `"boolean?"`, `"url"`, `"json"`, `"string"`)
|
|
17
|
+
- Loads `.env` automatically via `dotenv` (optional)
|
|
18
|
+
- Aggregated, friendly error output (shows all issues at once)
|
|
19
|
+
- TypeScript types inferred from the schema
|
|
20
|
+
- CLI for quick checks (great for CI)
|
|
21
|
+
- Minimal dependencies (runtime: `dotenv` only)
|
|
23
22
|
|
|
24
23
|
---
|
|
25
24
|
|
|
26
|
-
## ๐ฆ
|
|
25
|
+
## ๐ฆ Install
|
|
27
26
|
|
|
28
27
|
```bash
|
|
29
28
|
npm install env-typed-checker
|
|
30
29
|
```
|
|
31
|
-
|
|
30
|
+
|
|
31
|
+
## ๐ Quick Start (Code)
|
|
32
32
|
|
|
33
33
|
```ts
|
|
34
34
|
import { envDoctor } from "env-typed-checker";
|
|
@@ -39,10 +39,15 @@ export const config = envDoctor({
|
|
|
39
39
|
DEBUG: "boolean?"
|
|
40
40
|
});
|
|
41
41
|
```
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
*
|
|
42
|
+
|
|
43
|
+
### What you get
|
|
44
|
+
|
|
45
|
+
* PORT โ number
|
|
46
|
+
|
|
47
|
+
* DB_URL โ string (validated as URL)
|
|
48
|
+
|
|
49
|
+
* DEBUG โ boolean | undefined (optional)
|
|
50
|
+
|
|
46
51
|
|
|
47
52
|
### ๐งฉ Supported Types
|
|
48
53
|
| Type | Description |
|
|
@@ -53,26 +58,55 @@ export const config = envDoctor({
|
|
|
53
58
|
| **json** | Validates and parses a valid JSON string |
|
|
54
59
|
| **url** | Validates for a properly formatted URL |
|
|
55
60
|
|
|
61
|
+
|
|
56
62
|
### Optional Values
|
|
57
63
|
Add ? to make a variable optional:
|
|
58
64
|
```ts
|
|
59
|
-
{ DEBUG: "boolean?" }
|
|
65
|
+
envDoctor({ DEBUG: "boolean?" });
|
|
60
66
|
```
|
|
61
67
|
If missing โ value will be undefined.
|
|
62
68
|
|
|
69
|
+
|
|
70
|
+
### โ๏ธ Options
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
envDoctor(schema, {
|
|
74
|
+
loadDotEnv: true, // default: true (loads .env)
|
|
75
|
+
env: process.env // default: process.env (override for tests)
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### ๐งช Testing with custom env
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { envDoctor } from "env-typed-checker";
|
|
83
|
+
|
|
84
|
+
const cfg = envDoctor(
|
|
85
|
+
{ PORT: "number" },
|
|
86
|
+
{ loadDotEnv: false, env: { PORT: "3000" } }
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
console.log(cfg.PORT); // 3000
|
|
90
|
+
```
|
|
91
|
+
|
|
63
92
|
### โ Error Example
|
|
64
|
-
|
|
65
|
-
|
|
93
|
+
|
|
94
|
+
Given a `.env` like:
|
|
95
|
+
|
|
96
|
+
```env
|
|
66
97
|
PORT=abc
|
|
67
98
|
DB_URL=not-a-url
|
|
68
|
-
Code:
|
|
69
99
|
```
|
|
100
|
+
|
|
70
101
|
```ts
|
|
102
|
+
import { envDoctor } from "env-typed-checker";
|
|
103
|
+
|
|
71
104
|
envDoctor({
|
|
72
105
|
PORT: "number",
|
|
73
106
|
DB_URL: "url"
|
|
74
107
|
});
|
|
75
108
|
```
|
|
109
|
+
|
|
76
110
|
### Output:
|
|
77
111
|
|
|
78
112
|
```ts
|
|
@@ -82,24 +116,56 @@ ENV validation failed
|
|
|
82
116
|
```
|
|
83
117
|
All errors are shown together so you can fix them in one go.
|
|
84
118
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
119
|
+
# ๐ฅ๏ธ CLI
|
|
120
|
+
|
|
121
|
+
Validate your environment without writing code โ perfect for CI pipelines.
|
|
122
|
+
|
|
123
|
+
## 1) Create a schema file
|
|
124
|
+
|
|
125
|
+
`env.schema.json`
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"PORT": "number",
|
|
129
|
+
"DB_URL": "url",
|
|
130
|
+
"DEBUG": "boolean?"
|
|
131
|
+
}
|
|
91
132
|
```
|
|
92
|
-
### ๐งช Example with Custom Env (Testing)
|
|
93
133
|
|
|
94
|
-
|
|
95
|
-
const cfg = envDoctor(
|
|
96
|
-
{ PORT: "number" },
|
|
97
|
-
{ loadDotEnv: false, env: { PORT: "3000" } }
|
|
98
|
-
);
|
|
134
|
+
## 2) Run the check
|
|
99
135
|
|
|
100
|
-
|
|
136
|
+
```bash
|
|
137
|
+
npx env-typed-checker check --schema env.schema.json
|
|
101
138
|
```
|
|
102
|
-
|
|
139
|
+
|
|
140
|
+
### Options
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Custom env file
|
|
144
|
+
npx env-typed-checker check --schema env.schema.json --env-file .env.production
|
|
145
|
+
|
|
146
|
+
# Skip dotenv (use process.env only)
|
|
147
|
+
npx env-typed-checker check --schema env.schema.json --no-dotenv
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Exit codes
|
|
151
|
+
|
|
152
|
+
* `0` = OK
|
|
153
|
+
|
|
154
|
+
* `1` = validation failed
|
|
155
|
+
|
|
156
|
+
* `2` = CLI usage / unexpected error
|
|
157
|
+
|
|
158
|
+
## โ
CI Example (GitHub Actions)
|
|
159
|
+
|
|
160
|
+
Add this to your workflow to fail the build if env is invalid:
|
|
161
|
+
|
|
162
|
+
```yml
|
|
163
|
+
- name: Validate env
|
|
164
|
+
run: npx env-typed-checker check --schema env.schema.json
|
|
165
|
+
```
|
|
166
|
+
(If you use a different env file in CI, pass --env-file.)
|
|
167
|
+
|
|
168
|
+
## ๐ Development
|
|
103
169
|
Clone the repo and install:
|
|
104
170
|
```bash
|
|
105
171
|
npm install
|
|
@@ -111,43 +177,36 @@ npm run test # run tests
|
|
|
111
177
|
npm run typecheck # TypeScript check
|
|
112
178
|
npm run dev # watch build
|
|
113
179
|
```
|
|
180
|
+
|
|
114
181
|
### ๐ค Contributing
|
|
115
|
-
|
|
182
|
+
PRs are welcome!
|
|
183
|
+
|
|
184
|
+
* Add new validators (e.g. `enum`, `regex`, `email`)
|
|
185
|
+
|
|
186
|
+
* Improve CLI output formatting
|
|
187
|
+
|
|
188
|
+
* Add .env.example generator
|
|
189
|
+
|
|
190
|
+
* Improve docs & examples
|
|
116
191
|
|
|
117
|
-
* Improve error messages
|
|
118
|
-
* Add more boolean variants
|
|
119
|
-
* Enhance URL validation
|
|
120
|
-
* Add JSON schema validation
|
|
121
|
-
* Write better docs & examples
|
|
122
192
|
* Please read CONTRIBUTING.md before opening a PR.
|
|
123
193
|
|
|
124
194
|
### ๐ Roadmap
|
|
125
|
-
|
|
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
|
|
195
|
+
* `.env.example` generator
|
|
136
196
|
|
|
137
|
-
|
|
138
|
-
MIT
|
|
197
|
+
* Strict mode: warn on unknown variables
|
|
139
198
|
|
|
199
|
+
* More schema features: enums, defaults, min/max
|
|
140
200
|
|
|
141
|
-
|
|
201
|
+
* Framework helpers (Next.js / Express / etc.)
|
|
142
202
|
|
|
143
|
-
```yml
|
|
144
|
-
If you want, I can help you add:
|
|
145
203
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
- example project section
|
|
204
|
+
# ๐ License
|
|
205
|
+
MIT
|
|
149
206
|
|
|
150
|
-
Just tell me ๐
|
|
151
|
-
```
|
|
152
207
|
|
|
208
|
+
---
|
|
153
209
|
|
|
210
|
+
```yml
|
|
211
|
+
::contentReference[oaicite:0]{index=0}
|
|
212
|
+
```
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import * as dotenv from "dotenv";
|
|
3
|
+
|
|
4
|
+
// src/error.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/parsers.ts
|
|
16
|
+
function parseByType(type, raw) {
|
|
17
|
+
switch (type) {
|
|
18
|
+
case "string":
|
|
19
|
+
return raw;
|
|
20
|
+
case "number": {
|
|
21
|
+
const n = Number(raw.trim());
|
|
22
|
+
if (!Number.isFinite(n)) {
|
|
23
|
+
throw new Error(`expected number, got "${raw}"`);
|
|
24
|
+
}
|
|
25
|
+
return n;
|
|
26
|
+
}
|
|
27
|
+
case "boolean": {
|
|
28
|
+
const v = raw.trim().toLowerCase();
|
|
29
|
+
if (["true", "1", "yes", "y", "on"].includes(v)) return true;
|
|
30
|
+
if (["false", "0", "no", "n", "off"].includes(v)) return false;
|
|
31
|
+
throw new Error(
|
|
32
|
+
`expected boolean (true/false/1/0/yes/no/on/off), got "${raw}"`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
case "json": {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(raw);
|
|
38
|
+
} catch {
|
|
39
|
+
throw new Error(`expected json, got "${raw}"`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
case "url": {
|
|
43
|
+
try {
|
|
44
|
+
new URL(raw);
|
|
45
|
+
return raw;
|
|
46
|
+
} catch {
|
|
47
|
+
throw new Error(`expected url, got "${raw}"`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/* v8 ignore next -- @preserve */
|
|
51
|
+
default: {
|
|
52
|
+
const _never = type;
|
|
53
|
+
return _never;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function splitOptional(schemaValue) {
|
|
58
|
+
const optional = schemaValue.endsWith("?");
|
|
59
|
+
const base = optional ? schemaValue.slice(0, -1) : schemaValue;
|
|
60
|
+
const allowed = ["string", "number", "boolean", "json", "url"];
|
|
61
|
+
if (!allowed.includes(base)) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Unsupported type "${schemaValue}". Supported: string, number, boolean, json, url (optional with ?)`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return { baseType: base, optional };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/validator.ts
|
|
70
|
+
function validateAndParse(schema, env) {
|
|
71
|
+
const issues = [];
|
|
72
|
+
const out = {};
|
|
73
|
+
for (const [key, schemaValue] of Object.entries(schema)) {
|
|
74
|
+
let baseType;
|
|
75
|
+
let optional;
|
|
76
|
+
try {
|
|
77
|
+
({ baseType, optional } = splitOptional(schemaValue));
|
|
78
|
+
} catch (e) {
|
|
79
|
+
issues.push({
|
|
80
|
+
key,
|
|
81
|
+
kind: "invalid",
|
|
82
|
+
message: String(e)
|
|
83
|
+
});
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const raw = env[key];
|
|
87
|
+
if (raw === void 0 || raw === "") {
|
|
88
|
+
if (optional) {
|
|
89
|
+
out[key] = void 0;
|
|
90
|
+
} else {
|
|
91
|
+
issues.push({
|
|
92
|
+
key,
|
|
93
|
+
kind: "missing",
|
|
94
|
+
message: "missing required environment variable"
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
out[key] = parseByType(baseType, raw);
|
|
101
|
+
} catch (e) {
|
|
102
|
+
issues.push({
|
|
103
|
+
key,
|
|
104
|
+
kind: "invalid",
|
|
105
|
+
message: String(e)
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (issues.length > 0) {
|
|
110
|
+
throw new EnvDoctorError(issues);
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/index.ts
|
|
116
|
+
function envDoctor(schema, options = {}) {
|
|
117
|
+
const { loadDotEnv = true, env = process.env } = options;
|
|
118
|
+
if (loadDotEnv) dotenv.config();
|
|
119
|
+
return validateAndParse(schema, env);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export {
|
|
123
|
+
EnvDoctorError,
|
|
124
|
+
envDoctor
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=chunk-U6GH2TRS.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/error.ts","../src/parsers.ts","../src/validator.ts"],"sourcesContent":["import * as dotenv from \"dotenv\";\nimport { validateAndParse } from \"./validator\";\nimport type { EnvDoctorOptions, EnvDoctorResult, EnvDoctorSchema } from \"./types\";\n\nexport type { EnvDoctorOptions, EnvDoctorSchema } from \"./types\";\nexport { EnvDoctorError } from \"./error\";\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 } from \"./types\";\n\nexport function parseByType(type: EnvBaseType, raw: string): unknown {\n switch (type) {\n case \"string\":\n return raw;\n\n case \"number\": {\n // Trim to handle \" 3000 \"\n const n = Number(raw.trim());\n if (!Number.isFinite(n)) {\n throw new Error(`expected number, got \"${raw}\"`);\n }\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 // If it doesn't parse as a URL, this throws.\n // We return the original string (common for configs).\n new URL(raw);\n return raw;\n } catch {\n throw new Error(`expected url, got \"${raw}\"`);\n }\n }\n\n /* v8 ignore next -- @preserve */\n default: {\n // Exhaustive check (unreachable at runtime)\n const _never: never = type;\n return _never;\n }\n }\n}\n\nexport function splitOptional(schemaValue: string): {\n baseType: EnvBaseType;\n optional: boolean;\n} {\n const optional = schemaValue.endsWith(\"?\");\n const base = optional ? schemaValue.slice(0, -1) : schemaValue;\n\n // runtime safety\n const allowed = [\"string\", \"number\", \"boolean\", \"json\", \"url\"] as const;\n if (!allowed.includes(base as any)) {\n throw new Error(\n `Unsupported type \"${schemaValue}\". Supported: string, number, boolean, json, url (optional with ?)`,\n );\n }\n\n return { baseType: base as EnvBaseType, optional };\n}\n","import { EnvDoctorError, type EnvDoctorIssue } from \"./error\";\nimport { parseByType, splitOptional } from \"./parsers\";\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 baseType: any;\n let optional: boolean;\n\n try {\n ({ baseType, optional } = splitOptional(schemaValue));\n } catch (e) {\n issues.push({\n key,\n kind: \"invalid\",\n message: String(e),\n });\n continue;\n }\n\n const raw = env[key];\n\n if (raw === undefined || raw === \"\") {\n if (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 out[key] = parseByType(baseType, raw);\n } catch (e) {\n issues.push({\n key,\n kind: \"invalid\",\n message: String(e),\n });\n }\n }\n\n if (issues.length > 0) {\n throw new EnvDoctorError(issues);\n }\n\n return out as EnvDoctorResult<TSchema>;\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.d.mts
ADDED
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/cli.ts
|
|
31
|
+
var cli_exports = {};
|
|
32
|
+
__export(cli_exports, {
|
|
33
|
+
runCli: () => runCli
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(cli_exports);
|
|
36
|
+
var import_node_fs = __toESM(require("fs"));
|
|
37
|
+
var import_node_path = __toESM(require("path"));
|
|
38
|
+
var dotenv2 = __toESM(require("dotenv"));
|
|
39
|
+
|
|
40
|
+
// src/index.ts
|
|
41
|
+
var dotenv = __toESM(require("dotenv"));
|
|
42
|
+
|
|
43
|
+
// src/error.ts
|
|
44
|
+
var EnvDoctorError = class extends Error {
|
|
45
|
+
constructor(issues) {
|
|
46
|
+
const header = "ENV validation failed";
|
|
47
|
+
const lines = issues.map((i) => `- ${i.key}: ${i.message}`);
|
|
48
|
+
super([header, ...lines].join("\n"));
|
|
49
|
+
this.name = "EnvDoctorError";
|
|
50
|
+
this.issues = issues;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// src/parsers.ts
|
|
55
|
+
function parseByType(type, raw) {
|
|
56
|
+
switch (type) {
|
|
57
|
+
case "string":
|
|
58
|
+
return raw;
|
|
59
|
+
case "number": {
|
|
60
|
+
const n = Number(raw.trim());
|
|
61
|
+
if (!Number.isFinite(n)) {
|
|
62
|
+
throw new Error(`expected number, got "${raw}"`);
|
|
63
|
+
}
|
|
64
|
+
return n;
|
|
65
|
+
}
|
|
66
|
+
case "boolean": {
|
|
67
|
+
const v = raw.trim().toLowerCase();
|
|
68
|
+
if (["true", "1", "yes", "y", "on"].includes(v)) return true;
|
|
69
|
+
if (["false", "0", "no", "n", "off"].includes(v)) return false;
|
|
70
|
+
throw new Error(
|
|
71
|
+
`expected boolean (true/false/1/0/yes/no/on/off), got "${raw}"`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
case "json": {
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(raw);
|
|
77
|
+
} catch {
|
|
78
|
+
throw new Error(`expected json, got "${raw}"`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
case "url": {
|
|
82
|
+
try {
|
|
83
|
+
new URL(raw);
|
|
84
|
+
return raw;
|
|
85
|
+
} catch {
|
|
86
|
+
throw new Error(`expected url, got "${raw}"`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/* v8 ignore next -- @preserve */
|
|
90
|
+
default: {
|
|
91
|
+
const _never = type;
|
|
92
|
+
return _never;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function splitOptional(schemaValue) {
|
|
97
|
+
const optional = schemaValue.endsWith("?");
|
|
98
|
+
const base = optional ? schemaValue.slice(0, -1) : schemaValue;
|
|
99
|
+
const allowed = ["string", "number", "boolean", "json", "url"];
|
|
100
|
+
if (!allowed.includes(base)) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Unsupported type "${schemaValue}". Supported: string, number, boolean, json, url (optional with ?)`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return { baseType: base, optional };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/validator.ts
|
|
109
|
+
function validateAndParse(schema, env) {
|
|
110
|
+
const issues = [];
|
|
111
|
+
const out = {};
|
|
112
|
+
for (const [key, schemaValue] of Object.entries(schema)) {
|
|
113
|
+
let baseType;
|
|
114
|
+
let optional;
|
|
115
|
+
try {
|
|
116
|
+
({ baseType, optional } = splitOptional(schemaValue));
|
|
117
|
+
} catch (e) {
|
|
118
|
+
issues.push({
|
|
119
|
+
key,
|
|
120
|
+
kind: "invalid",
|
|
121
|
+
message: String(e)
|
|
122
|
+
});
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const raw = env[key];
|
|
126
|
+
if (raw === void 0 || raw === "") {
|
|
127
|
+
if (optional) {
|
|
128
|
+
out[key] = void 0;
|
|
129
|
+
} else {
|
|
130
|
+
issues.push({
|
|
131
|
+
key,
|
|
132
|
+
kind: "missing",
|
|
133
|
+
message: "missing required environment variable"
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
out[key] = parseByType(baseType, raw);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
issues.push({
|
|
142
|
+
key,
|
|
143
|
+
kind: "invalid",
|
|
144
|
+
message: String(e)
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (issues.length > 0) {
|
|
149
|
+
throw new EnvDoctorError(issues);
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/index.ts
|
|
155
|
+
function envDoctor(schema, options = {}) {
|
|
156
|
+
const { loadDotEnv = true, env = process.env } = options;
|
|
157
|
+
if (loadDotEnv) dotenv.config();
|
|
158
|
+
return validateAndParse(schema, env);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/cli.ts
|
|
162
|
+
function parseArgs(argv) {
|
|
163
|
+
const out = { useDotenv: true };
|
|
164
|
+
const [cmd, ...rest] = argv;
|
|
165
|
+
out.cmd = cmd;
|
|
166
|
+
for (let i = 0; i < rest.length; i++) {
|
|
167
|
+
const a = rest[i];
|
|
168
|
+
if (a === "--schema") {
|
|
169
|
+
out.schemaPath = rest[++i];
|
|
170
|
+
} else if (a.startsWith("--schema=")) {
|
|
171
|
+
out.schemaPath = a.split("=", 2)[1];
|
|
172
|
+
} else if (a === "--env-file") {
|
|
173
|
+
out.envFile = rest[++i];
|
|
174
|
+
} else if (a.startsWith("--env-file=")) {
|
|
175
|
+
out.envFile = a.split("=", 2)[1];
|
|
176
|
+
} else if (a === "--no-dotenv") {
|
|
177
|
+
out.useDotenv = false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
function loadSchema(schemaPath) {
|
|
183
|
+
const abs = import_node_path.default.resolve(process.cwd(), schemaPath);
|
|
184
|
+
const raw = import_node_fs.default.readFileSync(abs, "utf8");
|
|
185
|
+
const json = JSON.parse(raw);
|
|
186
|
+
if (!json || typeof json !== "object" || Array.isArray(json)) {
|
|
187
|
+
throw new Error("Schema must be a JSON object of key -> type.");
|
|
188
|
+
}
|
|
189
|
+
for (const [k, v] of Object.entries(json)) {
|
|
190
|
+
if (typeof k !== "string" || typeof v !== "string") {
|
|
191
|
+
throw new Error("Schema must be a JSON object of string -> string.");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return json;
|
|
195
|
+
}
|
|
196
|
+
function runCli(argv, io = console) {
|
|
197
|
+
const { cmd, schemaPath, envFile, useDotenv } = parseArgs(argv);
|
|
198
|
+
if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
199
|
+
io.log(
|
|
200
|
+
[
|
|
201
|
+
"env-typed-checker",
|
|
202
|
+
"",
|
|
203
|
+
"Usage:",
|
|
204
|
+
" env-typed-checker check --schema <file> [--env-file <file>] [--no-dotenv]",
|
|
205
|
+
"",
|
|
206
|
+
"Options:",
|
|
207
|
+
" --schema <file> Path to schema JSON (required)",
|
|
208
|
+
" --env-file <file> Env file path (default: .env)",
|
|
209
|
+
" --no-dotenv Do not load env file; use process.env only",
|
|
210
|
+
"",
|
|
211
|
+
"Exit codes:",
|
|
212
|
+
" 0 = OK, 1 = validation failed, 2 = CLI error"
|
|
213
|
+
].join("\n")
|
|
214
|
+
);
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
if (cmd !== "check") {
|
|
218
|
+
io.error(`Unknown command: ${cmd}`);
|
|
219
|
+
return 2;
|
|
220
|
+
}
|
|
221
|
+
if (!schemaPath) {
|
|
222
|
+
io.error("Missing required option: --schema <file>");
|
|
223
|
+
return 2;
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
if (useDotenv) {
|
|
227
|
+
const p = envFile ?? ".env";
|
|
228
|
+
dotenv2.config({ path: import_node_path.default.resolve(process.cwd(), p) });
|
|
229
|
+
}
|
|
230
|
+
const schema = loadSchema(schemaPath);
|
|
231
|
+
envDoctor(schema, { loadDotEnv: false, env: process.env });
|
|
232
|
+
io.log("\u2705 Environment is valid.");
|
|
233
|
+
return 0;
|
|
234
|
+
} catch (e) {
|
|
235
|
+
if (e instanceof EnvDoctorError) {
|
|
236
|
+
io.error(e.message);
|
|
237
|
+
return 1;
|
|
238
|
+
}
|
|
239
|
+
io.error(String(e));
|
|
240
|
+
return 2;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
244
|
+
0 && (module.exports = {
|
|
245
|
+
runCli
|
|
246
|
+
});
|
|
247
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/index.ts","../src/error.ts","../src/parsers.ts","../src/validator.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport * as dotenv from \"dotenv\";\nimport { envDoctor, EnvDoctorError } from \"./index\";\nimport type { EnvDoctorSchema } from \"./types\";\n\ntype Io = {\n log: (msg: string) => void;\n error: (msg: string) => void;\n};\n\nfunction parseArgs(argv: string[]) {\n const out: {\n cmd?: string;\n schemaPath?: string;\n envFile?: string;\n useDotenv: boolean;\n } = { useDotenv: true };\n\n const [cmd, ...rest] = argv;\n out.cmd = cmd;\n\n for (let i = 0; i < rest.length; i++) {\n const a = rest[i];\n\n if (a === \"--schema\") {\n out.schemaPath = rest[++i];\n } else if (a.startsWith(\"--schema=\")) {\n out.schemaPath = a.split(\"=\", 2)[1];\n } else if (a === \"--env-file\") {\n out.envFile = rest[++i];\n } else if (a.startsWith(\"--env-file=\")) {\n out.envFile = a.split(\"=\", 2)[1];\n } else if (a === \"--no-dotenv\") {\n out.useDotenv = false;\n }\n }\n\n return out;\n}\n\nfunction loadSchema(schemaPath: string): EnvDoctorSchema {\n const abs = path.resolve(process.cwd(), schemaPath);\n const raw = fs.readFileSync(abs, \"utf8\");\n const json = JSON.parse(raw);\n\n if (!json || typeof json !== \"object\" || Array.isArray(json)) {\n throw new Error(\"Schema must be a JSON object of key -> type.\");\n }\n\n // Ensure values are strings\n for (const [k, v] of Object.entries(json)) {\n if (typeof k !== \"string\" || typeof v !== \"string\") {\n throw new Error(\"Schema must be a JSON object of string -> string.\");\n }\n }\n\n return json as EnvDoctorSchema;\n}\n\nexport function runCli(argv: string[], io: Io = console): number {\n const { cmd, schemaPath, envFile, useDotenv } = parseArgs(argv);\n\n if (!cmd || cmd === \"help\" || cmd === \"--help\" || cmd === \"-h\") {\n io.log(\n [\n \"env-typed-checker\",\n \"\",\n \"Usage:\",\n \" env-typed-checker check --schema <file> [--env-file <file>] [--no-dotenv]\",\n \"\",\n \"Options:\",\n \" --schema <file> Path to schema JSON (required)\",\n \" --env-file <file> Env file path (default: .env)\",\n \" --no-dotenv Do not load env file; use process.env only\",\n \"\",\n \"Exit codes:\",\n \" 0 = OK, 1 = validation failed, 2 = CLI error\",\n ].join(\"\\n\"),\n );\n return 0;\n }\n\n if (cmd !== \"check\") {\n io.error(`Unknown command: ${cmd}`);\n return 2;\n }\n\n if (!schemaPath) {\n io.error(\"Missing required option: --schema <file>\");\n return 2;\n }\n\n try {\n if (useDotenv) {\n const p = envFile ?? \".env\";\n dotenv.config({ path: path.resolve(process.cwd(), p) });\n }\n\n const schema = loadSchema(schemaPath);\n\n // envDoctor will validate and throw EnvDoctorError if invalid\n envDoctor(schema, { loadDotEnv: false, env: process.env });\n\n io.log(\"โ
Environment is valid.\");\n return 0;\n } catch (e) {\n if (e instanceof EnvDoctorError) {\n io.error(e.message);\n return 1;\n }\n io.error(String(e));\n return 2;\n }\n}\n","import * as dotenv from \"dotenv\";\nimport { validateAndParse } from \"./validator\";\nimport type { EnvDoctorOptions, EnvDoctorResult, EnvDoctorSchema } from \"./types\";\n\nexport type { EnvDoctorOptions, EnvDoctorSchema } from \"./types\";\nexport { EnvDoctorError } from \"./error\";\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 } from \"./types\";\n\nexport function parseByType(type: EnvBaseType, raw: string): unknown {\n switch (type) {\n case \"string\":\n return raw;\n\n case \"number\": {\n // Trim to handle \" 3000 \"\n const n = Number(raw.trim());\n if (!Number.isFinite(n)) {\n throw new Error(`expected number, got \"${raw}\"`);\n }\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 // If it doesn't parse as a URL, this throws.\n // We return the original string (common for configs).\n new URL(raw);\n return raw;\n } catch {\n throw new Error(`expected url, got \"${raw}\"`);\n }\n }\n\n /* v8 ignore next -- @preserve */\n default: {\n // Exhaustive check (unreachable at runtime)\n const _never: never = type;\n return _never;\n }\n }\n}\n\nexport function splitOptional(schemaValue: string): {\n baseType: EnvBaseType;\n optional: boolean;\n} {\n const optional = schemaValue.endsWith(\"?\");\n const base = optional ? schemaValue.slice(0, -1) : schemaValue;\n\n // runtime safety\n const allowed = [\"string\", \"number\", \"boolean\", \"json\", \"url\"] as const;\n if (!allowed.includes(base as any)) {\n throw new Error(\n `Unsupported type \"${schemaValue}\". Supported: string, number, boolean, json, url (optional with ?)`,\n );\n }\n\n return { baseType: base as EnvBaseType, optional };\n}\n","import { EnvDoctorError, type EnvDoctorIssue } from \"./error\";\nimport { parseByType, splitOptional } from \"./parsers\";\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 baseType: any;\n let optional: boolean;\n\n try {\n ({ baseType, optional } = splitOptional(schemaValue));\n } catch (e) {\n issues.push({\n key,\n kind: \"invalid\",\n message: String(e),\n });\n continue;\n }\n\n const raw = env[key];\n\n if (raw === undefined || raw === \"\") {\n if (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 out[key] = parseByType(baseType, raw);\n } catch (e) {\n issues.push({\n key,\n kind: \"invalid\",\n message: String(e),\n });\n }\n }\n\n if (issues.length > 0) {\n throw new EnvDoctorError(issues);\n }\n\n return out as EnvDoctorResult<TSchema>;\n}\n"],"mappings":"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","names":["dotenv","path","fs"]}
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EnvDoctorError,
|
|
3
|
+
envDoctor
|
|
4
|
+
} from "./chunk-U6GH2TRS.mjs";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import * as dotenv from "dotenv";
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const out = { useDotenv: true };
|
|
12
|
+
const [cmd, ...rest] = argv;
|
|
13
|
+
out.cmd = cmd;
|
|
14
|
+
for (let i = 0; i < rest.length; i++) {
|
|
15
|
+
const a = rest[i];
|
|
16
|
+
if (a === "--schema") {
|
|
17
|
+
out.schemaPath = rest[++i];
|
|
18
|
+
} else if (a.startsWith("--schema=")) {
|
|
19
|
+
out.schemaPath = a.split("=", 2)[1];
|
|
20
|
+
} else if (a === "--env-file") {
|
|
21
|
+
out.envFile = rest[++i];
|
|
22
|
+
} else if (a.startsWith("--env-file=")) {
|
|
23
|
+
out.envFile = a.split("=", 2)[1];
|
|
24
|
+
} else if (a === "--no-dotenv") {
|
|
25
|
+
out.useDotenv = false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
function loadSchema(schemaPath) {
|
|
31
|
+
const abs = path.resolve(process.cwd(), schemaPath);
|
|
32
|
+
const raw = fs.readFileSync(abs, "utf8");
|
|
33
|
+
const json = JSON.parse(raw);
|
|
34
|
+
if (!json || typeof json !== "object" || Array.isArray(json)) {
|
|
35
|
+
throw new Error("Schema must be a JSON object of key -> type.");
|
|
36
|
+
}
|
|
37
|
+
for (const [k, v] of Object.entries(json)) {
|
|
38
|
+
if (typeof k !== "string" || typeof v !== "string") {
|
|
39
|
+
throw new Error("Schema must be a JSON object of string -> string.");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return json;
|
|
43
|
+
}
|
|
44
|
+
function runCli(argv, io = console) {
|
|
45
|
+
const { cmd, schemaPath, envFile, useDotenv } = parseArgs(argv);
|
|
46
|
+
if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
47
|
+
io.log(
|
|
48
|
+
[
|
|
49
|
+
"env-typed-checker",
|
|
50
|
+
"",
|
|
51
|
+
"Usage:",
|
|
52
|
+
" env-typed-checker check --schema <file> [--env-file <file>] [--no-dotenv]",
|
|
53
|
+
"",
|
|
54
|
+
"Options:",
|
|
55
|
+
" --schema <file> Path to schema JSON (required)",
|
|
56
|
+
" --env-file <file> Env file path (default: .env)",
|
|
57
|
+
" --no-dotenv Do not load env file; use process.env only",
|
|
58
|
+
"",
|
|
59
|
+
"Exit codes:",
|
|
60
|
+
" 0 = OK, 1 = validation failed, 2 = CLI error"
|
|
61
|
+
].join("\n")
|
|
62
|
+
);
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
if (cmd !== "check") {
|
|
66
|
+
io.error(`Unknown command: ${cmd}`);
|
|
67
|
+
return 2;
|
|
68
|
+
}
|
|
69
|
+
if (!schemaPath) {
|
|
70
|
+
io.error("Missing required option: --schema <file>");
|
|
71
|
+
return 2;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
if (useDotenv) {
|
|
75
|
+
const p = envFile ?? ".env";
|
|
76
|
+
dotenv.config({ path: path.resolve(process.cwd(), p) });
|
|
77
|
+
}
|
|
78
|
+
const schema = loadSchema(schemaPath);
|
|
79
|
+
envDoctor(schema, { loadDotEnv: false, env: process.env });
|
|
80
|
+
io.log("\u2705 Environment is valid.");
|
|
81
|
+
return 0;
|
|
82
|
+
} catch (e) {
|
|
83
|
+
if (e instanceof EnvDoctorError) {
|
|
84
|
+
io.error(e.message);
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
io.error(String(e));
|
|
88
|
+
return 2;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
runCli
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport * as dotenv from \"dotenv\";\nimport { envDoctor, EnvDoctorError } from \"./index\";\nimport type { EnvDoctorSchema } from \"./types\";\n\ntype Io = {\n log: (msg: string) => void;\n error: (msg: string) => void;\n};\n\nfunction parseArgs(argv: string[]) {\n const out: {\n cmd?: string;\n schemaPath?: string;\n envFile?: string;\n useDotenv: boolean;\n } = { useDotenv: true };\n\n const [cmd, ...rest] = argv;\n out.cmd = cmd;\n\n for (let i = 0; i < rest.length; i++) {\n const a = rest[i];\n\n if (a === \"--schema\") {\n out.schemaPath = rest[++i];\n } else if (a.startsWith(\"--schema=\")) {\n out.schemaPath = a.split(\"=\", 2)[1];\n } else if (a === \"--env-file\") {\n out.envFile = rest[++i];\n } else if (a.startsWith(\"--env-file=\")) {\n out.envFile = a.split(\"=\", 2)[1];\n } else if (a === \"--no-dotenv\") {\n out.useDotenv = false;\n }\n }\n\n return out;\n}\n\nfunction loadSchema(schemaPath: string): EnvDoctorSchema {\n const abs = path.resolve(process.cwd(), schemaPath);\n const raw = fs.readFileSync(abs, \"utf8\");\n const json = JSON.parse(raw);\n\n if (!json || typeof json !== \"object\" || Array.isArray(json)) {\n throw new Error(\"Schema must be a JSON object of key -> type.\");\n }\n\n // Ensure values are strings\n for (const [k, v] of Object.entries(json)) {\n if (typeof k !== \"string\" || typeof v !== \"string\") {\n throw new Error(\"Schema must be a JSON object of string -> string.\");\n }\n }\n\n return json as EnvDoctorSchema;\n}\n\nexport function runCli(argv: string[], io: Io = console): number {\n const { cmd, schemaPath, envFile, useDotenv } = parseArgs(argv);\n\n if (!cmd || cmd === \"help\" || cmd === \"--help\" || cmd === \"-h\") {\n io.log(\n [\n \"env-typed-checker\",\n \"\",\n \"Usage:\",\n \" env-typed-checker check --schema <file> [--env-file <file>] [--no-dotenv]\",\n \"\",\n \"Options:\",\n \" --schema <file> Path to schema JSON (required)\",\n \" --env-file <file> Env file path (default: .env)\",\n \" --no-dotenv Do not load env file; use process.env only\",\n \"\",\n \"Exit codes:\",\n \" 0 = OK, 1 = validation failed, 2 = CLI error\",\n ].join(\"\\n\"),\n );\n return 0;\n }\n\n if (cmd !== \"check\") {\n io.error(`Unknown command: ${cmd}`);\n return 2;\n }\n\n if (!schemaPath) {\n io.error(\"Missing required option: --schema <file>\");\n return 2;\n }\n\n try {\n if (useDotenv) {\n const p = envFile ?? \".env\";\n dotenv.config({ path: path.resolve(process.cwd(), p) });\n }\n\n const schema = loadSchema(schemaPath);\n\n // envDoctor will validate and throw EnvDoctorError if invalid\n envDoctor(schema, { loadDotEnv: false, env: process.env });\n\n io.log(\"โ
Environment is valid.\");\n return 0;\n } catch (e) {\n if (e instanceof EnvDoctorError) {\n io.error(e.message);\n return 1;\n }\n io.error(String(e));\n return 2;\n }\n}\n"],"mappings":";;;;;;AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,YAAY,YAAY;AASxB,SAAS,UAAU,MAAgB;AACjC,QAAM,MAKF,EAAE,WAAW,KAAK;AAEtB,QAAM,CAAC,KAAK,GAAG,IAAI,IAAI;AACvB,MAAI,MAAM;AAEV,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,IAAI,KAAK,CAAC;AAEhB,QAAI,MAAM,YAAY;AACpB,UAAI,aAAa,KAAK,EAAE,CAAC;AAAA,IAC3B,WAAW,EAAE,WAAW,WAAW,GAAG;AACpC,UAAI,aAAa,EAAE,MAAM,KAAK,CAAC,EAAE,CAAC;AAAA,IACpC,WAAW,MAAM,cAAc;AAC7B,UAAI,UAAU,KAAK,EAAE,CAAC;AAAA,IACxB,WAAW,EAAE,WAAW,aAAa,GAAG;AACtC,UAAI,UAAU,EAAE,MAAM,KAAK,CAAC,EAAE,CAAC;AAAA,IACjC,WAAW,MAAM,eAAe;AAC9B,UAAI,YAAY;AAAA,IAClB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,WAAW,YAAqC;AACvD,QAAM,MAAM,KAAK,QAAQ,QAAQ,IAAI,GAAG,UAAU;AAClD,QAAM,MAAM,GAAG,aAAa,KAAK,MAAM;AACvC,QAAM,OAAO,KAAK,MAAM,GAAG;AAE3B,MAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GAAG;AAC5D,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AAGA,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAAI,GAAG;AACzC,QAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU;AAClD,YAAM,IAAI,MAAM,mDAAmD;AAAA,IACrE;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,OAAO,MAAgB,KAAS,SAAiB;AAC/D,QAAM,EAAE,KAAK,YAAY,SAAS,UAAU,IAAI,UAAU,IAAI;AAE9D,MAAI,CAAC,OAAO,QAAQ,UAAU,QAAQ,YAAY,QAAQ,MAAM;AAC9D,OAAG;AAAA,MACD;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AACA,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ,SAAS;AACnB,OAAG,MAAM,oBAAoB,GAAG,EAAE;AAClC,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,YAAY;AACf,OAAG,MAAM,0CAA0C;AACnD,WAAO;AAAA,EACT;AAEA,MAAI;AACF,QAAI,WAAW;AACb,YAAM,IAAI,WAAW;AACrB,MAAO,cAAO,EAAE,MAAM,KAAK,QAAQ,QAAQ,IAAI,GAAG,CAAC,EAAE,CAAC;AAAA,IACxD;AAEA,UAAM,SAAS,WAAW,UAAU;AAGpC,cAAU,QAAQ,EAAE,YAAY,OAAO,KAAK,QAAQ,IAAI,CAAC;AAEzD,OAAG,IAAI,8BAAyB;AAChC,WAAO;AAAA,EACT,SAAS,GAAG;AACV,QAAI,aAAa,gBAAgB;AAC/B,SAAG,MAAM,EAAE,OAAO;AAClB,aAAO;AAAA,IACT;AACA,OAAG,MAAM,OAAO,CAAC,CAAC;AAClB,WAAO;AAAA,EACT;AACF;","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,123 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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/parsers.ts
|
|
16
|
-
function parseByType(type, raw) {
|
|
17
|
-
switch (type) {
|
|
18
|
-
case "string":
|
|
19
|
-
return raw;
|
|
20
|
-
case "number": {
|
|
21
|
-
const n = Number(raw.trim());
|
|
22
|
-
if (!Number.isFinite(n)) {
|
|
23
|
-
throw new Error(`expected number, got "${raw}"`);
|
|
24
|
-
}
|
|
25
|
-
return n;
|
|
26
|
-
}
|
|
27
|
-
case "boolean": {
|
|
28
|
-
const v = raw.trim().toLowerCase();
|
|
29
|
-
if (["true", "1", "yes", "y", "on"].includes(v)) return true;
|
|
30
|
-
if (["false", "0", "no", "n", "off"].includes(v)) return false;
|
|
31
|
-
throw new Error(
|
|
32
|
-
`expected boolean (true/false/1/0/yes/no/on/off), got "${raw}"`
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
case "json": {
|
|
36
|
-
try {
|
|
37
|
-
return JSON.parse(raw);
|
|
38
|
-
} catch {
|
|
39
|
-
throw new Error(`expected json, got "${raw}"`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
case "url": {
|
|
43
|
-
try {
|
|
44
|
-
new URL(raw);
|
|
45
|
-
return raw;
|
|
46
|
-
} catch {
|
|
47
|
-
throw new Error(`expected url, got "${raw}"`);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/* v8 ignore next -- @preserve */
|
|
51
|
-
default: {
|
|
52
|
-
const _never = type;
|
|
53
|
-
return _never;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
function splitOptional(schemaValue) {
|
|
58
|
-
const optional = schemaValue.endsWith("?");
|
|
59
|
-
const base = optional ? schemaValue.slice(0, -1) : schemaValue;
|
|
60
|
-
const allowed = ["string", "number", "boolean", "json", "url"];
|
|
61
|
-
if (!allowed.includes(base)) {
|
|
62
|
-
throw new Error(
|
|
63
|
-
`Unsupported type "${schemaValue}". Supported: string, number, boolean, json, url (optional with ?)`
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
return { baseType: base, optional };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// src/validator.ts
|
|
70
|
-
function validateAndParse(schema, env) {
|
|
71
|
-
const issues = [];
|
|
72
|
-
const out = {};
|
|
73
|
-
for (const [key, schemaValue] of Object.entries(schema)) {
|
|
74
|
-
let baseType;
|
|
75
|
-
let optional;
|
|
76
|
-
try {
|
|
77
|
-
({ baseType, optional } = splitOptional(schemaValue));
|
|
78
|
-
} catch (e) {
|
|
79
|
-
issues.push({
|
|
80
|
-
key,
|
|
81
|
-
kind: "invalid",
|
|
82
|
-
message: String(e)
|
|
83
|
-
});
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
const raw = env[key];
|
|
87
|
-
if (raw === void 0 || raw === "") {
|
|
88
|
-
if (optional) {
|
|
89
|
-
out[key] = void 0;
|
|
90
|
-
} else {
|
|
91
|
-
issues.push({
|
|
92
|
-
key,
|
|
93
|
-
kind: "missing",
|
|
94
|
-
message: "missing required environment variable"
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
try {
|
|
100
|
-
out[key] = parseByType(baseType, raw);
|
|
101
|
-
} catch (e) {
|
|
102
|
-
issues.push({
|
|
103
|
-
key,
|
|
104
|
-
kind: "invalid",
|
|
105
|
-
message: String(e)
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
if (issues.length > 0) {
|
|
110
|
-
throw new EnvDoctorError(issues);
|
|
111
|
-
}
|
|
112
|
-
return out;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// src/index.ts
|
|
116
|
-
function envDoctor(schema, options = {}) {
|
|
117
|
-
const { loadDotEnv = true, env = process.env } = options;
|
|
118
|
-
if (loadDotEnv) dotenv.config();
|
|
119
|
-
return validateAndParse(schema, env);
|
|
120
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
EnvDoctorError,
|
|
3
|
+
envDoctor
|
|
4
|
+
} from "./chunk-U6GH2TRS.mjs";
|
|
121
5
|
export {
|
|
122
6
|
EnvDoctorError,
|
|
123
7
|
envDoctor
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "env-typed-checker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Validate and parse environment variables with a tiny, type-safe schema.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"module": "dist/index.mjs",
|
|
10
10
|
"types": "dist/index.d.ts",
|
|
11
|
+
"bin": {
|
|
12
|
+
"env-typed-checker": "./bin/env-typed-checker.cjs"
|
|
13
|
+
},
|
|
11
14
|
"exports": {
|
|
12
15
|
".": {
|
|
13
16
|
"types": "./dist/index.d.ts",
|
|
@@ -17,6 +20,7 @@
|
|
|
17
20
|
},
|
|
18
21
|
"files": [
|
|
19
22
|
"dist",
|
|
23
|
+
"bin",
|
|
20
24
|
"README.md",
|
|
21
25
|
"LICENSE"
|
|
22
26
|
],
|