env-typed-checker 0.2.0 → 0.2.3
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 +128 -41
- package/bin/env-typed-checker.cjs +3 -2
- package/dist/chunk-L5DK6LRX.mjs +262 -0
- package/dist/chunk-L5DK6LRX.mjs.map +1 -0
- package/dist/cli/index.js +479 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.mjs +190 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/index.d.mts +31 -16
- package/dist/index.d.ts +31 -16
- package/dist/index.js +174 -37
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-U6GH2TRS.mjs +0 -126
- package/dist/chunk-U6GH2TRS.mjs.map +0 -1
- package/dist/cli.js +0 -247
- package/dist/cli.js.map +0 -1
- package/dist/cli.mjs +0 -94
- package/dist/cli.mjs.map +0 -1
- /package/dist/{cli.d.mts → cli/index.d.mts} +0 -0
- /package/dist/{cli.d.ts → cli/index.d.ts} +0 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EnvDoctorError,
|
|
3
|
+
envDoctor,
|
|
4
|
+
normalizeSpec
|
|
5
|
+
} from "../chunk-L5DK6LRX.mjs";
|
|
6
|
+
|
|
7
|
+
// src/cli/cli.ts
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import * as dotenv from "dotenv";
|
|
11
|
+
function parseArgs(argv) {
|
|
12
|
+
const out = { useDotenv: true, useDefaults: true, commentTypes: false };
|
|
13
|
+
const [cmd, ...rest] = argv;
|
|
14
|
+
out.cmd = cmd;
|
|
15
|
+
for (let i = 0; i < rest.length; i++) {
|
|
16
|
+
const a = rest[i];
|
|
17
|
+
if (a === "--schema") out.schemaPath = rest[++i];
|
|
18
|
+
else if (a.startsWith("--schema=")) out.schemaPath = a.split("=", 2)[1];
|
|
19
|
+
else if (a === "--env-file") out.envFile = rest[++i];
|
|
20
|
+
else if (a.startsWith("--env-file=")) out.envFile = a.split("=", 2)[1];
|
|
21
|
+
else if (a === "--no-dotenv") out.useDotenv = false;
|
|
22
|
+
else if (a === "--out") out.outFile = rest[++i];
|
|
23
|
+
else if (a.startsWith("--out=")) out.outFile = a.split("=", 2)[1];
|
|
24
|
+
else if (a === "--mode") {
|
|
25
|
+
const m = rest[++i];
|
|
26
|
+
if (m === "update" || m === "create") out.mode = m;
|
|
27
|
+
} else if (a.startsWith("--mode=")) {
|
|
28
|
+
const m = a.split("=", 2)[1];
|
|
29
|
+
if (m === "update" || m === "create") out.mode = m;
|
|
30
|
+
} else if (a === "--no-defaults") out.useDefaults = false;
|
|
31
|
+
else if (a === "--comment-types") out.commentTypes = true;
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function loadSchema(schemaPath) {
|
|
36
|
+
const abs = path.resolve(process.cwd(), schemaPath);
|
|
37
|
+
const raw = fs.readFileSync(abs, "utf8");
|
|
38
|
+
const json = JSON.parse(raw);
|
|
39
|
+
if (!json || typeof json !== "object" || Array.isArray(json)) {
|
|
40
|
+
throw new Error("Schema must be a JSON object of key -> spec.");
|
|
41
|
+
}
|
|
42
|
+
for (const [k, v] of Object.entries(json)) {
|
|
43
|
+
try {
|
|
44
|
+
normalizeSpec(v);
|
|
45
|
+
} catch (e) {
|
|
46
|
+
throw new Error(`Invalid schema value for "${k}": ${String(e)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return json;
|
|
50
|
+
}
|
|
51
|
+
function buildEnv(useDotenv, envFile) {
|
|
52
|
+
const env = { ...process.env };
|
|
53
|
+
if (!useDotenv) return env;
|
|
54
|
+
const p = envFile ?? ".env";
|
|
55
|
+
const envAbsPath = path.resolve(process.cwd(), p);
|
|
56
|
+
if (fs.existsSync(envAbsPath)) {
|
|
57
|
+
const fileRaw = fs.readFileSync(envAbsPath, "utf8");
|
|
58
|
+
const parsed = dotenv.parse(fileRaw);
|
|
59
|
+
Object.assign(env, parsed);
|
|
60
|
+
}
|
|
61
|
+
return env;
|
|
62
|
+
}
|
|
63
|
+
function readEnvFile(filePath) {
|
|
64
|
+
if (!fs.existsSync(filePath)) return {};
|
|
65
|
+
const raw = fs.readFileSync(filePath, "utf8").replace(/^export\s+/gm, "");
|
|
66
|
+
return dotenv.parse(raw);
|
|
67
|
+
}
|
|
68
|
+
function getTypeLabel(spec) {
|
|
69
|
+
if (typeof spec === "string") return spec;
|
|
70
|
+
return spec.type;
|
|
71
|
+
}
|
|
72
|
+
function defaultToEnvString(x) {
|
|
73
|
+
if (x === void 0 || x === null) return "";
|
|
74
|
+
if (typeof x === "string") return x;
|
|
75
|
+
if (typeof x === "number" || typeof x === "boolean") return String(x);
|
|
76
|
+
return JSON.stringify(x);
|
|
77
|
+
}
|
|
78
|
+
function needsQuoting(v) {
|
|
79
|
+
if (v === "") return true;
|
|
80
|
+
return /[\s#\n"']/.test(v);
|
|
81
|
+
}
|
|
82
|
+
function formatEnvLine(key, value, comment) {
|
|
83
|
+
const safe = needsQuoting(value) ? JSON.stringify(value) : value;
|
|
84
|
+
return comment ? `${key}=${safe} # ${comment}` : `${key}=${safe}`;
|
|
85
|
+
}
|
|
86
|
+
function runGenerate(schema, outPath, mode, useDefaults, commentTypes, io) {
|
|
87
|
+
const absOut = path.resolve(process.cwd(), outPath);
|
|
88
|
+
if (mode === "create" && fs.existsSync(absOut)) {
|
|
89
|
+
io.error(`Refusing to overwrite existing file: ${outPath}`);
|
|
90
|
+
return 2;
|
|
91
|
+
}
|
|
92
|
+
const existing = mode === "update" ? readEnvFile(absOut) : {};
|
|
93
|
+
const keys = Object.keys(schema).sort();
|
|
94
|
+
const linesToAdd = [];
|
|
95
|
+
for (const key of keys) {
|
|
96
|
+
if (existing[key] !== void 0) continue;
|
|
97
|
+
const spec = schema[key];
|
|
98
|
+
const typeLabel = commentTypes ? getTypeLabel(spec) : void 0;
|
|
99
|
+
let val = "";
|
|
100
|
+
if (useDefaults) {
|
|
101
|
+
const ns = normalizeSpec(spec);
|
|
102
|
+
if (ns.defaultValue !== void 0) {
|
|
103
|
+
val = defaultToEnvString(ns.defaultValue);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
linesToAdd.push(formatEnvLine(key, val, typeLabel));
|
|
107
|
+
}
|
|
108
|
+
if (linesToAdd.length === 0) {
|
|
109
|
+
io.log("\u2705 No missing variables. Nothing to generate.");
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
if (mode === "create") {
|
|
113
|
+
fs.writeFileSync(absOut, linesToAdd.join("\n") + "\n", "utf8");
|
|
114
|
+
io.log(`\u2705 Created ${outPath} with ${linesToAdd.length} variables.`);
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
const prefix = fs.existsSync(absOut) ? "\n" : "";
|
|
118
|
+
fs.appendFileSync(absOut, prefix + linesToAdd.join("\n") + "\n", "utf8");
|
|
119
|
+
io.log(
|
|
120
|
+
`\u2705 Updated ${outPath}. Added ${linesToAdd.length} missing variables.`
|
|
121
|
+
);
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
function runCli(argv, io = console) {
|
|
125
|
+
const {
|
|
126
|
+
cmd,
|
|
127
|
+
schemaPath,
|
|
128
|
+
envFile,
|
|
129
|
+
useDotenv,
|
|
130
|
+
outFile,
|
|
131
|
+
mode,
|
|
132
|
+
useDefaults,
|
|
133
|
+
commentTypes
|
|
134
|
+
} = parseArgs(argv);
|
|
135
|
+
if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
136
|
+
io.log(
|
|
137
|
+
[
|
|
138
|
+
"env-typed-checker",
|
|
139
|
+
"",
|
|
140
|
+
"Usage:",
|
|
141
|
+
" env-typed-checker check --schema <file> [--env-file <file>] [--no-dotenv]",
|
|
142
|
+
" env-typed-checker generate --schema <file> [--out <file>] [--mode update|create] [--no-defaults] [--comment-types]",
|
|
143
|
+
"",
|
|
144
|
+
"Options:",
|
|
145
|
+
" --schema <file> Path to schema JSON (required)",
|
|
146
|
+
" --env-file <file> Env file path (default: .env) [check]",
|
|
147
|
+
" --no-dotenv Do not load env file; use process.env only [check]",
|
|
148
|
+
" --out <file> Output env file (default: .env) [generate]",
|
|
149
|
+
" --mode update|create update appends missing keys; create fails if file exists (default: update)",
|
|
150
|
+
" --no-defaults do not write schema defaults; write empty placeholders [generate]",
|
|
151
|
+
" --comment-types add inline comments with type info [generate]",
|
|
152
|
+
"",
|
|
153
|
+
"Exit codes:",
|
|
154
|
+
" 0 = OK, 1 = validation failed, 2 = CLI error"
|
|
155
|
+
].join("\n")
|
|
156
|
+
);
|
|
157
|
+
return 0;
|
|
158
|
+
}
|
|
159
|
+
if (!schemaPath) {
|
|
160
|
+
io.error("Missing required option: --schema <file>");
|
|
161
|
+
return 2;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const schema = loadSchema(schemaPath);
|
|
165
|
+
if (cmd === "check") {
|
|
166
|
+
const env = buildEnv(useDotenv, envFile);
|
|
167
|
+
envDoctor(schema, { loadDotEnv: false, env });
|
|
168
|
+
io.log("\u2705 Environment is valid.");
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
if (cmd === "generate") {
|
|
172
|
+
const out = outFile ?? ".env";
|
|
173
|
+
const m = mode ?? "update";
|
|
174
|
+
return runGenerate(schema, out, m, useDefaults, commentTypes, io);
|
|
175
|
+
}
|
|
176
|
+
io.error(`Unknown command: ${cmd}`);
|
|
177
|
+
return 2;
|
|
178
|
+
} catch (e) {
|
|
179
|
+
if (e instanceof EnvDoctorError) {
|
|
180
|
+
io.error(e.message);
|
|
181
|
+
return 1;
|
|
182
|
+
}
|
|
183
|
+
io.error(e instanceof Error ? e.message : String(e));
|
|
184
|
+
return 2;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
export {
|
|
188
|
+
runCli
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/cli.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport * as dotenv from \"dotenv\";\nimport { envDoctor, EnvDoctorError } from \"../index\";\nimport { normalizeSpec } from \"../validators/primitives\";\nimport type { EnvDoctorSchema, EnvSchemaValue } 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\n // generate options\n outFile?: string;\n mode?: \"update\" | \"create\";\n useDefaults: boolean;\n commentTypes: boolean;\n } = { useDotenv: true, useDefaults: true, commentTypes: false };\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\") out.schemaPath = rest[++i];\n else if (a.startsWith(\"--schema=\")) out.schemaPath = a.split(\"=\", 2)[1];\n else if (a === \"--env-file\") out.envFile = rest[++i];\n else if (a.startsWith(\"--env-file=\")) out.envFile = a.split(\"=\", 2)[1];\n else if (a === \"--no-dotenv\") out.useDotenv = false;\n // generate flags\n else if (a === \"--out\") out.outFile = rest[++i];\n else if (a.startsWith(\"--out=\")) out.outFile = a.split(\"=\", 2)[1];\n else if (a === \"--mode\") {\n const m = rest[++i];\n if (m === \"update\" || m === \"create\") out.mode = m;\n } else if (a.startsWith(\"--mode=\")) {\n const m = a.split(\"=\", 2)[1];\n if (m === \"update\" || m === \"create\") out.mode = m;\n } else if (a === \"--no-defaults\") out.useDefaults = false;\n else if (a === \"--comment-types\") out.commentTypes = true;\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\n // Let JSON.parse throw with its native message; CLI catches and prints it\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 -> spec.\");\n }\n\n // Validate using the same core logic (reduces CLI branch complexity a lot)\n for (const [k, v] of Object.entries(json)) {\n try {\n normalizeSpec(v as EnvSchemaValue);\n } catch (e) {\n throw new Error(`Invalid schema value for \"${k}\": ${String(e)}`);\n }\n }\n\n return json as EnvDoctorSchema;\n}\n\nfunction buildEnv(\n useDotenv: boolean,\n envFile?: string,\n): Record<string, string | undefined> {\n const env: Record<string, string | undefined> = { ...process.env };\n\n if (!useDotenv) return env;\n\n const p = envFile ?? \".env\";\n const envAbsPath = path.resolve(process.cwd(), p);\n\n if (fs.existsSync(envAbsPath)) {\n const fileRaw = fs.readFileSync(envAbsPath, \"utf8\");\n const parsed = dotenv.parse(fileRaw);\n Object.assign(env, parsed); // env-file overrides shell env\n }\n\n return env;\n}\n\n// -------- generate helpers --------\n\nfunction readEnvFile(filePath: string): Record<string, string> {\n if (!fs.existsSync(filePath)) return {};\n // allow \"export KEY=...\" lines too\n const raw = fs.readFileSync(filePath, \"utf8\").replace(/^export\\s+/gm, \"\");\n return dotenv.parse(raw);\n}\n\nfunction getTypeLabel(spec: EnvSchemaValue): string {\n // loadSchema() already validates specs are string or object\n if (typeof spec === \"string\") return spec;\n return (spec as any).type as string;\n}\n\nfunction defaultToEnvString(x: unknown): string {\n if (x === undefined || x === null) return \"\";\n if (typeof x === \"string\") return x;\n if (typeof x === \"number\" || typeof x === \"boolean\") return String(x);\n\n // Schema defaults come from JSON => JSON.stringify won't throw and won't return undefined\n return JSON.stringify(x) as string;\n}\n\nfunction needsQuoting(v: string): boolean {\n if (v === \"\") return true;\n return /[\\s#\\n\"']/.test(v);\n}\n\nfunction formatEnvLine(key: string, value: string, comment?: string): string {\n const safe = needsQuoting(value) ? JSON.stringify(value) : value;\n return comment ? `${key}=${safe} # ${comment}` : `${key}=${safe}`;\n}\n\nfunction runGenerate(\n schema: EnvDoctorSchema,\n outPath: string,\n mode: \"update\" | \"create\",\n useDefaults: boolean,\n commentTypes: boolean,\n io: Io,\n): number {\n const absOut = path.resolve(process.cwd(), outPath);\n\n if (mode === \"create\" && fs.existsSync(absOut)) {\n io.error(`Refusing to overwrite existing file: ${outPath}`);\n return 2;\n }\n\n const existing = mode === \"update\" ? readEnvFile(absOut) : {};\n const keys = Object.keys(schema).sort();\n\n const linesToAdd: string[] = [];\n\n for (const key of keys) {\n if (existing[key] !== undefined) continue; // do not overwrite existing values (even empty)\n\n const spec = schema[key];\n const typeLabel = commentTypes ? getTypeLabel(spec) : undefined;\n\n let val = \"\";\n\n if (useDefaults) {\n const ns = normalizeSpec(spec);\n if (ns.defaultValue !== undefined) {\n val = defaultToEnvString(ns.defaultValue);\n }\n }\n\n linesToAdd.push(formatEnvLine(key, val, typeLabel));\n }\n\n if (linesToAdd.length === 0) {\n io.log(\"✅ No missing variables. Nothing to generate.\");\n return 0;\n }\n\n if (mode === \"create\") {\n fs.writeFileSync(absOut, linesToAdd.join(\"\\n\") + \"\\n\", \"utf8\");\n io.log(`✅ Created ${outPath} with ${linesToAdd.length} variables.`);\n return 0;\n }\n\n // update mode: append\n const prefix = fs.existsSync(absOut) ? \"\\n\" : \"\";\n fs.appendFileSync(absOut, prefix + linesToAdd.join(\"\\n\") + \"\\n\", \"utf8\");\n io.log(\n `✅ Updated ${outPath}. Added ${linesToAdd.length} missing variables.`,\n );\n return 0;\n}\n\n// -------- main --------\n\nexport function runCli(argv: string[], io: Io = console): number {\n const {\n cmd,\n schemaPath,\n envFile,\n useDotenv,\n outFile,\n mode,\n useDefaults,\n commentTypes,\n } = 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 \" env-typed-checker generate --schema <file> [--out <file>] [--mode update|create] [--no-defaults] [--comment-types]\",\n \"\",\n \"Options:\",\n \" --schema <file> Path to schema JSON (required)\",\n \" --env-file <file> Env file path (default: .env) [check]\",\n \" --no-dotenv Do not load env file; use process.env only [check]\",\n \" --out <file> Output env file (default: .env) [generate]\",\n \" --mode update|create update appends missing keys; create fails if file exists (default: update)\",\n \" --no-defaults do not write schema defaults; write empty placeholders [generate]\",\n \" --comment-types add inline comments with type info [generate]\",\n \"\",\n \"Exit codes:\",\n \" 0 = OK, 1 = validation failed, 2 = CLI error\",\n ].join(\"\\n\"),\n );\n return 0;\n }\n\n if (!schemaPath) {\n io.error(\"Missing required option: --schema <file>\");\n return 2;\n }\n\n try {\n const schema = loadSchema(schemaPath);\n\n if (cmd === \"check\") {\n const env = buildEnv(useDotenv, envFile);\n envDoctor(schema, { loadDotEnv: false, env });\n io.log(\"✅ Environment is valid.\");\n return 0;\n }\n\n if (cmd === \"generate\") {\n const out = outFile ?? \".env\";\n const m = mode ?? \"update\";\n return runGenerate(schema, out, m, useDefaults, commentTypes, io);\n }\n\n io.error(`Unknown command: ${cmd}`);\n return 2;\n } catch (e) {\n if (e instanceof EnvDoctorError) {\n io.error(e.message);\n return 1;\n }\n io.error(e instanceof Error ? e.message : String(e));\n return 2;\n }\n}\n"],"mappings":";;;;;;;AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,YAAY,YAAY;AAUxB,SAAS,UAAU,MAAgB;AACjC,QAAM,MAWF,EAAE,WAAW,MAAM,aAAa,MAAM,cAAc,MAAM;AAE9D,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,WAAY,KAAI,aAAa,KAAK,EAAE,CAAC;AAAA,aACtC,EAAE,WAAW,WAAW,EAAG,KAAI,aAAa,EAAE,MAAM,KAAK,CAAC,EAAE,CAAC;AAAA,aAC7D,MAAM,aAAc,KAAI,UAAU,KAAK,EAAE,CAAC;AAAA,aAC1C,EAAE,WAAW,aAAa,EAAG,KAAI,UAAU,EAAE,MAAM,KAAK,CAAC,EAAE,CAAC;AAAA,aAC5D,MAAM,cAAe,KAAI,YAAY;AAAA,aAErC,MAAM,QAAS,KAAI,UAAU,KAAK,EAAE,CAAC;AAAA,aACrC,EAAE,WAAW,QAAQ,EAAG,KAAI,UAAU,EAAE,MAAM,KAAK,CAAC,EAAE,CAAC;AAAA,aACvD,MAAM,UAAU;AACvB,YAAM,IAAI,KAAK,EAAE,CAAC;AAClB,UAAI,MAAM,YAAY,MAAM,SAAU,KAAI,OAAO;AAAA,IACnD,WAAW,EAAE,WAAW,SAAS,GAAG;AAClC,YAAM,IAAI,EAAE,MAAM,KAAK,CAAC,EAAE,CAAC;AAC3B,UAAI,MAAM,YAAY,MAAM,SAAU,KAAI,OAAO;AAAA,IACnD,WAAW,MAAM,gBAAiB,KAAI,cAAc;AAAA,aAC3C,MAAM,kBAAmB,KAAI,eAAe;AAAA,EACvD;AAEA,SAAO;AACT;AAEA,SAAS,WAAW,YAAqC;AACvD,QAAM,MAAM,KAAK,QAAQ,QAAQ,IAAI,GAAG,UAAU;AAClD,QAAM,MAAM,GAAG,aAAa,KAAK,MAAM;AAGvC,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;AACF,oBAAc,CAAmB;AAAA,IACnC,SAAS,GAAG;AACV,YAAM,IAAI,MAAM,6BAA6B,CAAC,MAAM,OAAO,CAAC,CAAC,EAAE;AAAA,IACjE;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,SACP,WACA,SACoC;AACpC,QAAM,MAA0C,EAAE,GAAG,QAAQ,IAAI;AAEjE,MAAI,CAAC,UAAW,QAAO;AAEvB,QAAM,IAAI,WAAW;AACrB,QAAM,aAAa,KAAK,QAAQ,QAAQ,IAAI,GAAG,CAAC;AAEhD,MAAI,GAAG,WAAW,UAAU,GAAG;AAC7B,UAAM,UAAU,GAAG,aAAa,YAAY,MAAM;AAClD,UAAM,SAAgB,aAAM,OAAO;AACnC,WAAO,OAAO,KAAK,MAAM;AAAA,EAC3B;AAEA,SAAO;AACT;AAIA,SAAS,YAAY,UAA0C;AAC7D,MAAI,CAAC,GAAG,WAAW,QAAQ,EAAG,QAAO,CAAC;AAEtC,QAAM,MAAM,GAAG,aAAa,UAAU,MAAM,EAAE,QAAQ,gBAAgB,EAAE;AACxE,SAAc,aAAM,GAAG;AACzB;AAEA,SAAS,aAAa,MAA8B;AAElD,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,SAAQ,KAAa;AACvB;AAEA,SAAS,mBAAmB,GAAoB;AAC9C,MAAI,MAAM,UAAa,MAAM,KAAM,QAAO;AAC1C,MAAI,OAAO,MAAM,SAAU,QAAO;AAClC,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAW,QAAO,OAAO,CAAC;AAGpE,SAAO,KAAK,UAAU,CAAC;AACzB;AAEA,SAAS,aAAa,GAAoB;AACxC,MAAI,MAAM,GAAI,QAAO;AACrB,SAAO,YAAY,KAAK,CAAC;AAC3B;AAEA,SAAS,cAAc,KAAa,OAAe,SAA0B;AAC3E,QAAM,OAAO,aAAa,KAAK,IAAI,KAAK,UAAU,KAAK,IAAI;AAC3D,SAAO,UAAU,GAAG,GAAG,IAAI,IAAI,MAAM,OAAO,KAAK,GAAG,GAAG,IAAI,IAAI;AACjE;AAEA,SAAS,YACP,QACA,SACA,MACA,aACA,cACA,IACQ;AACR,QAAM,SAAS,KAAK,QAAQ,QAAQ,IAAI,GAAG,OAAO;AAElD,MAAI,SAAS,YAAY,GAAG,WAAW,MAAM,GAAG;AAC9C,OAAG,MAAM,wCAAwC,OAAO,EAAE;AAC1D,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,SAAS,WAAW,YAAY,MAAM,IAAI,CAAC;AAC5D,QAAM,OAAO,OAAO,KAAK,MAAM,EAAE,KAAK;AAEtC,QAAM,aAAuB,CAAC;AAE9B,aAAW,OAAO,MAAM;AACtB,QAAI,SAAS,GAAG,MAAM,OAAW;AAEjC,UAAM,OAAO,OAAO,GAAG;AACvB,UAAM,YAAY,eAAe,aAAa,IAAI,IAAI;AAEtD,QAAI,MAAM;AAEV,QAAI,aAAa;AACf,YAAM,KAAK,cAAc,IAAI;AAC7B,UAAI,GAAG,iBAAiB,QAAW;AACjC,cAAM,mBAAmB,GAAG,YAAY;AAAA,MAC1C;AAAA,IACF;AAEA,eAAW,KAAK,cAAc,KAAK,KAAK,SAAS,CAAC;AAAA,EACpD;AAEA,MAAI,WAAW,WAAW,GAAG;AAC3B,OAAG,IAAI,mDAA8C;AACrD,WAAO;AAAA,EACT;AAEA,MAAI,SAAS,UAAU;AACrB,OAAG,cAAc,QAAQ,WAAW,KAAK,IAAI,IAAI,MAAM,MAAM;AAC7D,OAAG,IAAI,kBAAa,OAAO,SAAS,WAAW,MAAM,aAAa;AAClE,WAAO;AAAA,EACT;AAGA,QAAM,SAAS,GAAG,WAAW,MAAM,IAAI,OAAO;AAC9C,KAAG,eAAe,QAAQ,SAAS,WAAW,KAAK,IAAI,IAAI,MAAM,MAAM;AACvE,KAAG;AAAA,IACD,kBAAa,OAAO,WAAW,WAAW,MAAM;AAAA,EAClD;AACA,SAAO;AACT;AAIO,SAAS,OAAO,MAAgB,KAAS,SAAiB;AAC/D,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,UAAU,IAAI;AAElB,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,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AACA,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,YAAY;AACf,OAAG,MAAM,0CAA0C;AACnD,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,SAAS,WAAW,UAAU;AAEpC,QAAI,QAAQ,SAAS;AACnB,YAAM,MAAM,SAAS,WAAW,OAAO;AACvC,gBAAU,QAAQ,EAAE,YAAY,OAAO,IAAI,CAAC;AAC5C,SAAG,IAAI,8BAAyB;AAChC,aAAO;AAAA,IACT;AAEA,QAAI,QAAQ,YAAY;AACtB,YAAM,MAAM,WAAW;AACvB,YAAM,IAAI,QAAQ;AAClB,aAAO,YAAY,QAAQ,KAAK,GAAG,aAAa,cAAc,EAAE;AAAA,IAClE;AAEA,OAAG,MAAM,oBAAoB,GAAG,EAAE;AAClC,WAAO;AAAA,EACT,SAAS,GAAG;AACV,QAAI,aAAa,gBAAgB;AAC/B,SAAG,MAAM,EAAE,OAAO;AAClB,aAAO;AAAA,IACT;AACA,OAAG,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AACnD,WAAO;AAAA,EACT;AACF;","names":[]}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,26 +1,41 @@
|
|
|
1
|
-
type EnvBaseType = "string" | "number" | "boolean" | "json" | "url";
|
|
2
|
-
type
|
|
1
|
+
type EnvBaseType = "string" | "number" | "boolean" | "json" | "url" | "email";
|
|
2
|
+
type EnvStringSpec = EnvBaseType | `${EnvBaseType}?`;
|
|
3
|
+
type EnumSpec<T extends readonly string[] = readonly string[]> = {
|
|
4
|
+
type: "enum";
|
|
5
|
+
values: T;
|
|
6
|
+
optional?: boolean;
|
|
7
|
+
};
|
|
8
|
+
type RegexSpec = {
|
|
9
|
+
type: "regex";
|
|
10
|
+
pattern: string;
|
|
11
|
+
flags?: string;
|
|
12
|
+
optional?: boolean;
|
|
13
|
+
};
|
|
14
|
+
type PrimitiveObjectSpec = {
|
|
15
|
+
type: EnvBaseType;
|
|
16
|
+
optional?: boolean;
|
|
17
|
+
default?: unknown;
|
|
18
|
+
};
|
|
19
|
+
type EnvSchemaValue = EnvStringSpec | EnumSpec | RegexSpec | PrimitiveObjectSpec;
|
|
3
20
|
type EnvDoctorSchema = Record<string, EnvSchemaValue>;
|
|
4
21
|
type EnvDoctorOptions = {
|
|
5
|
-
/**
|
|
6
|
-
* Load .env automatically using dotenv.
|
|
7
|
-
* Default: true
|
|
8
|
-
*/
|
|
9
22
|
loadDotEnv?: boolean;
|
|
10
|
-
/**
|
|
11
|
-
* Override env source (useful for tests)
|
|
12
|
-
* Default: process.env
|
|
13
|
-
*/
|
|
14
23
|
env?: Record<string, string | undefined>;
|
|
15
|
-
/**
|
|
16
|
-
* If true, throw on unknown env vars is NOT implemented in v1.
|
|
17
|
-
* Placeholder for future.
|
|
18
|
-
*/
|
|
19
24
|
strict?: boolean;
|
|
20
25
|
};
|
|
21
|
-
|
|
26
|
+
/** ---- Type inference ---- */
|
|
27
|
+
type StripOptional<S extends string> = S extends `${infer T}?` ? T : S;
|
|
28
|
+
type InferBase<T extends EnvBaseType> = T extends "string" ? string : T extends "number" ? number : T extends "boolean" ? boolean : T extends "json" ? unknown : T extends "url" ? string : T extends "email" ? string : never;
|
|
29
|
+
type InferFromStringSpec<S extends EnvStringSpec> = InferBase<StripOptional<S> & EnvBaseType>;
|
|
30
|
+
type InferFromValue<V> = V extends EnvStringSpec ? InferFromStringSpec<V> : V extends EnumSpec<infer T> ? T[number] : V extends RegexSpec ? string : never;
|
|
31
|
+
type IsOptional<V> = V extends `${string}?` ? true : V extends {
|
|
32
|
+
optional: true;
|
|
33
|
+
} ? true : false;
|
|
34
|
+
type HasDefault<V> = V extends {
|
|
35
|
+
default: any;
|
|
36
|
+
} ? true : false;
|
|
22
37
|
type EnvDoctorResult<TSchema extends EnvDoctorSchema> = {
|
|
23
|
-
[K in keyof TSchema]: TSchema[K] extends
|
|
38
|
+
[K in keyof TSchema]: HasDefault<TSchema[K]> extends true ? InferFromValue<TSchema[K]> : IsOptional<TSchema[K]> extends true ? InferFromValue<TSchema[K]> | undefined : InferFromValue<TSchema[K]>;
|
|
24
39
|
};
|
|
25
40
|
|
|
26
41
|
type EnvDoctorIssue = {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,26 +1,41 @@
|
|
|
1
|
-
type EnvBaseType = "string" | "number" | "boolean" | "json" | "url";
|
|
2
|
-
type
|
|
1
|
+
type EnvBaseType = "string" | "number" | "boolean" | "json" | "url" | "email";
|
|
2
|
+
type EnvStringSpec = EnvBaseType | `${EnvBaseType}?`;
|
|
3
|
+
type EnumSpec<T extends readonly string[] = readonly string[]> = {
|
|
4
|
+
type: "enum";
|
|
5
|
+
values: T;
|
|
6
|
+
optional?: boolean;
|
|
7
|
+
};
|
|
8
|
+
type RegexSpec = {
|
|
9
|
+
type: "regex";
|
|
10
|
+
pattern: string;
|
|
11
|
+
flags?: string;
|
|
12
|
+
optional?: boolean;
|
|
13
|
+
};
|
|
14
|
+
type PrimitiveObjectSpec = {
|
|
15
|
+
type: EnvBaseType;
|
|
16
|
+
optional?: boolean;
|
|
17
|
+
default?: unknown;
|
|
18
|
+
};
|
|
19
|
+
type EnvSchemaValue = EnvStringSpec | EnumSpec | RegexSpec | PrimitiveObjectSpec;
|
|
3
20
|
type EnvDoctorSchema = Record<string, EnvSchemaValue>;
|
|
4
21
|
type EnvDoctorOptions = {
|
|
5
|
-
/**
|
|
6
|
-
* Load .env automatically using dotenv.
|
|
7
|
-
* Default: true
|
|
8
|
-
*/
|
|
9
22
|
loadDotEnv?: boolean;
|
|
10
|
-
/**
|
|
11
|
-
* Override env source (useful for tests)
|
|
12
|
-
* Default: process.env
|
|
13
|
-
*/
|
|
14
23
|
env?: Record<string, string | undefined>;
|
|
15
|
-
/**
|
|
16
|
-
* If true, throw on unknown env vars is NOT implemented in v1.
|
|
17
|
-
* Placeholder for future.
|
|
18
|
-
*/
|
|
19
24
|
strict?: boolean;
|
|
20
25
|
};
|
|
21
|
-
|
|
26
|
+
/** ---- Type inference ---- */
|
|
27
|
+
type StripOptional<S extends string> = S extends `${infer T}?` ? T : S;
|
|
28
|
+
type InferBase<T extends EnvBaseType> = T extends "string" ? string : T extends "number" ? number : T extends "boolean" ? boolean : T extends "json" ? unknown : T extends "url" ? string : T extends "email" ? string : never;
|
|
29
|
+
type InferFromStringSpec<S extends EnvStringSpec> = InferBase<StripOptional<S> & EnvBaseType>;
|
|
30
|
+
type InferFromValue<V> = V extends EnvStringSpec ? InferFromStringSpec<V> : V extends EnumSpec<infer T> ? T[number] : V extends RegexSpec ? string : never;
|
|
31
|
+
type IsOptional<V> = V extends `${string}?` ? true : V extends {
|
|
32
|
+
optional: true;
|
|
33
|
+
} ? true : false;
|
|
34
|
+
type HasDefault<V> = V extends {
|
|
35
|
+
default: any;
|
|
36
|
+
} ? true : false;
|
|
22
37
|
type EnvDoctorResult<TSchema extends EnvDoctorSchema> = {
|
|
23
|
-
[K in keyof TSchema]: TSchema[K] extends
|
|
38
|
+
[K in keyof TSchema]: HasDefault<TSchema[K]> extends true ? InferFromValue<TSchema[K]> : IsOptional<TSchema[K]> extends true ? InferFromValue<TSchema[K]> | undefined : InferFromValue<TSchema[K]>;
|
|
24
39
|
};
|
|
25
40
|
|
|
26
41
|
type EnvDoctorIssue = {
|
package/dist/index.js
CHANGED
|
@@ -34,9 +34,11 @@ __export(index_exports, {
|
|
|
34
34
|
envDoctor: () => envDoctor
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/core/envDoctor.ts
|
|
37
39
|
var dotenv = __toESM(require("dotenv"));
|
|
38
40
|
|
|
39
|
-
// src/
|
|
41
|
+
// src/errors/EnvDoctorError.ts
|
|
40
42
|
var EnvDoctorError = class extends Error {
|
|
41
43
|
constructor(issues) {
|
|
42
44
|
const header = "ENV validation failed";
|
|
@@ -47,16 +49,15 @@ var EnvDoctorError = class extends Error {
|
|
|
47
49
|
}
|
|
48
50
|
};
|
|
49
51
|
|
|
50
|
-
// src/
|
|
52
|
+
// src/validators/primitives.ts
|
|
53
|
+
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
51
54
|
function parseByType(type, raw) {
|
|
52
55
|
switch (type) {
|
|
53
56
|
case "string":
|
|
54
57
|
return raw;
|
|
55
58
|
case "number": {
|
|
56
59
|
const n = Number(raw.trim());
|
|
57
|
-
if (!Number.isFinite(n)) {
|
|
58
|
-
throw new Error(`expected number, got "${raw}"`);
|
|
59
|
-
}
|
|
60
|
+
if (!Number.isFinite(n)) throw new Error(`expected number, got "${raw}"`);
|
|
60
61
|
return n;
|
|
61
62
|
}
|
|
62
63
|
case "boolean": {
|
|
@@ -82,45 +83,172 @@ function parseByType(type, raw) {
|
|
|
82
83
|
throw new Error(`expected url, got "${raw}"`);
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
|
-
|
|
86
|
+
case "email": {
|
|
87
|
+
const s = raw.trim();
|
|
88
|
+
if (!EMAIL_RE.test(s)) {
|
|
89
|
+
throw new Error(`expected email, got "${raw}"`);
|
|
90
|
+
}
|
|
91
|
+
return s;
|
|
92
|
+
}
|
|
86
93
|
default: {
|
|
87
|
-
|
|
88
|
-
return _never;
|
|
94
|
+
throw new Error(`unsupported primitive type: ${String(type)}`);
|
|
89
95
|
}
|
|
90
96
|
}
|
|
91
97
|
}
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
function hasOwn(obj, key) {
|
|
99
|
+
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
100
|
+
}
|
|
101
|
+
function coerceDefault(kind, def) {
|
|
102
|
+
if (def === void 0) return void 0;
|
|
103
|
+
switch (kind) {
|
|
104
|
+
case "string": {
|
|
105
|
+
if (typeof def !== "string")
|
|
106
|
+
throw new Error(`default for string must be a string`);
|
|
107
|
+
return def;
|
|
108
|
+
}
|
|
109
|
+
case "number": {
|
|
110
|
+
if (typeof def === "number") {
|
|
111
|
+
if (!Number.isFinite(def))
|
|
112
|
+
throw new Error(`default for number must be finite`);
|
|
113
|
+
return def;
|
|
114
|
+
}
|
|
115
|
+
if (typeof def === "string") return parseByType("number", def);
|
|
116
|
+
throw new Error(`default for number must be number or string`);
|
|
117
|
+
}
|
|
118
|
+
case "boolean": {
|
|
119
|
+
if (typeof def === "boolean") return def;
|
|
120
|
+
if (typeof def === "string") return parseByType("boolean", def);
|
|
121
|
+
throw new Error(`default for boolean must be boolean or string`);
|
|
122
|
+
}
|
|
123
|
+
case "json": {
|
|
124
|
+
return def;
|
|
125
|
+
}
|
|
126
|
+
case "url": {
|
|
127
|
+
if (typeof def !== "string")
|
|
128
|
+
throw new Error(`default for url must be a string`);
|
|
129
|
+
return parseByType("url", def);
|
|
130
|
+
}
|
|
131
|
+
case "email": {
|
|
132
|
+
if (typeof def !== "string")
|
|
133
|
+
throw new Error(`default for email must be a string`);
|
|
134
|
+
return parseByType("email", def);
|
|
135
|
+
}
|
|
136
|
+
/* c8 ignore next */
|
|
137
|
+
default: {
|
|
138
|
+
throw new Error(`unsupported default kind: ${String(kind)}`);
|
|
139
|
+
}
|
|
100
140
|
}
|
|
101
|
-
|
|
141
|
+
}
|
|
142
|
+
function normalizeSpec(schemaValue) {
|
|
143
|
+
if (typeof schemaValue === "string") {
|
|
144
|
+
const optional = schemaValue.endsWith("?");
|
|
145
|
+
const base = optional ? schemaValue.slice(0, -1) : schemaValue;
|
|
146
|
+
const allowed = [
|
|
147
|
+
"string",
|
|
148
|
+
"number",
|
|
149
|
+
"boolean",
|
|
150
|
+
"json",
|
|
151
|
+
"url",
|
|
152
|
+
"email"
|
|
153
|
+
];
|
|
154
|
+
if (!allowed.includes(base)) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`Unsupported type "${schemaValue}". Supported: string, number, boolean, json, url, email (optional with ?)`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return { kind: base, optional };
|
|
160
|
+
}
|
|
161
|
+
if (!schemaValue || typeof schemaValue !== "object" || Array.isArray(schemaValue)) {
|
|
162
|
+
throw new Error("Schema value must be a string or object spec.");
|
|
163
|
+
}
|
|
164
|
+
const t = schemaValue.type;
|
|
165
|
+
const primitiveAllowed = [
|
|
166
|
+
"string",
|
|
167
|
+
"number",
|
|
168
|
+
"boolean",
|
|
169
|
+
"json",
|
|
170
|
+
"url",
|
|
171
|
+
"email"
|
|
172
|
+
];
|
|
173
|
+
if (primitiveAllowed.includes(t)) {
|
|
174
|
+
const optional = !!schemaValue.optional;
|
|
175
|
+
const defaultValue = hasOwn(schemaValue, "default") ? coerceDefault(t, schemaValue.default) : void 0;
|
|
176
|
+
return { kind: t, optional, defaultValue };
|
|
177
|
+
}
|
|
178
|
+
if (t === "enum") {
|
|
179
|
+
const values = schemaValue.values;
|
|
180
|
+
if (!Array.isArray(values) || values.length === 0 || !values.every((v) => typeof v === "string")) {
|
|
181
|
+
throw new Error(`enum spec requires "values": string[] (non-empty)`);
|
|
182
|
+
}
|
|
183
|
+
const optional = !!schemaValue.optional;
|
|
184
|
+
let defaultValue = void 0;
|
|
185
|
+
if (hasOwn(schemaValue, "default")) {
|
|
186
|
+
const def = schemaValue.default;
|
|
187
|
+
if (typeof def !== "string") {
|
|
188
|
+
throw new Error(`default for enum must be a string`);
|
|
189
|
+
}
|
|
190
|
+
if (!values.includes(def)) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`default "${def}" must be one of [${values.join(", ")}]`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
defaultValue = def;
|
|
196
|
+
}
|
|
197
|
+
return { kind: "enum", optional, values, defaultValue };
|
|
198
|
+
}
|
|
199
|
+
if (t === "regex") {
|
|
200
|
+
const pattern = schemaValue.pattern;
|
|
201
|
+
const flags = schemaValue.flags;
|
|
202
|
+
if (typeof pattern !== "string" || pattern.length === 0) {
|
|
203
|
+
throw new Error(`regex spec requires "pattern": string`);
|
|
204
|
+
}
|
|
205
|
+
if (flags !== void 0 && typeof flags !== "string") {
|
|
206
|
+
throw new Error(`regex spec "flags" must be a string if provided`);
|
|
207
|
+
}
|
|
208
|
+
let re;
|
|
209
|
+
try {
|
|
210
|
+
re = new RegExp(pattern, flags);
|
|
211
|
+
} catch (e) {
|
|
212
|
+
throw new Error(`invalid regex: ${String(e)}`);
|
|
213
|
+
}
|
|
214
|
+
const display = `/${pattern}/${flags ?? ""}`;
|
|
215
|
+
const optional = !!schemaValue.optional;
|
|
216
|
+
let defaultValue = void 0;
|
|
217
|
+
if (hasOwn(schemaValue, "default")) {
|
|
218
|
+
const def = schemaValue.default;
|
|
219
|
+
if (typeof def !== "string") {
|
|
220
|
+
throw new Error(`default for regex must be a string`);
|
|
221
|
+
}
|
|
222
|
+
if (!re.test(def)) {
|
|
223
|
+
throw new Error(`default "${def}" does not match ${display}`);
|
|
224
|
+
}
|
|
225
|
+
defaultValue = def;
|
|
226
|
+
}
|
|
227
|
+
return { kind: "regex", optional, re, display, defaultValue };
|
|
228
|
+
}
|
|
229
|
+
throw new Error(
|
|
230
|
+
`Unsupported object spec type "${String(t)}". Supported: primitives (string/number/boolean/json/url/email), enum, regex`
|
|
231
|
+
);
|
|
102
232
|
}
|
|
103
233
|
|
|
104
|
-
// src/
|
|
234
|
+
// src/core/runner.ts
|
|
105
235
|
function validateAndParse(schema, env) {
|
|
106
236
|
const issues = [];
|
|
107
237
|
const out = {};
|
|
108
238
|
for (const [key, schemaValue] of Object.entries(schema)) {
|
|
109
|
-
let
|
|
110
|
-
let optional;
|
|
239
|
+
let spec;
|
|
111
240
|
try {
|
|
112
|
-
|
|
241
|
+
spec = normalizeSpec(schemaValue);
|
|
113
242
|
} catch (e) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
kind: "invalid",
|
|
117
|
-
message: String(e)
|
|
118
|
-
});
|
|
243
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
244
|
+
issues.push({ key, kind: "invalid", message: msg });
|
|
119
245
|
continue;
|
|
120
246
|
}
|
|
121
247
|
const raw = env[key];
|
|
122
248
|
if (raw === void 0 || raw === "") {
|
|
123
|
-
if (
|
|
249
|
+
if (spec.defaultValue !== void 0) {
|
|
250
|
+
out[key] = spec.defaultValue;
|
|
251
|
+
} else if (spec.optional) {
|
|
124
252
|
out[key] = void 0;
|
|
125
253
|
} else {
|
|
126
254
|
issues.push({
|
|
@@ -132,22 +260,31 @@ function validateAndParse(schema, env) {
|
|
|
132
260
|
continue;
|
|
133
261
|
}
|
|
134
262
|
try {
|
|
135
|
-
|
|
263
|
+
if (spec.kind === "enum") {
|
|
264
|
+
if (!spec.values.includes(raw)) {
|
|
265
|
+
throw new Error(
|
|
266
|
+
`expected one of [${spec.values.join(", ")}], got "${raw}"`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
out[key] = raw;
|
|
270
|
+
} else if (spec.kind === "regex") {
|
|
271
|
+
if (!spec.re.test(raw)) {
|
|
272
|
+
throw new Error(`does not match ${spec.display}`);
|
|
273
|
+
}
|
|
274
|
+
out[key] = raw;
|
|
275
|
+
} else {
|
|
276
|
+
out[key] = parseByType(spec.kind, raw);
|
|
277
|
+
}
|
|
136
278
|
} catch (e) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
kind: "invalid",
|
|
140
|
-
message: String(e)
|
|
141
|
-
});
|
|
279
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
280
|
+
issues.push({ key, kind: "invalid", message: msg });
|
|
142
281
|
}
|
|
143
282
|
}
|
|
144
|
-
if (issues.length > 0)
|
|
145
|
-
throw new EnvDoctorError(issues);
|
|
146
|
-
}
|
|
283
|
+
if (issues.length > 0) throw new EnvDoctorError(issues);
|
|
147
284
|
return out;
|
|
148
285
|
}
|
|
149
286
|
|
|
150
|
-
// src/
|
|
287
|
+
// src/core/envDoctor.ts
|
|
151
288
|
function envDoctor(schema, options = {}) {
|
|
152
289
|
const { loadDotEnv = true, env = process.env } = options;
|
|
153
290
|
if (loadDotEnv) dotenv.config();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,aAAwB;;;ACIjB,IAAM,iBAAN,cAA6B,MAAM;AAAA,EAGxC,YAAY,QAA0B;AACpC,UAAM,SAAS;AACf,UAAM,QAAQ,OAAO,IAAI,CAAC,MAAM,KAAK,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE;AAC1D,UAAM,CAAC,QAAQ,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC;AACnC,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;;;ACZO,SAAS,YAAY,MAAmB,KAAsB;AACnE,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO;AAAA,IAET,KAAK,UAAU;AAEb,YAAM,IAAI,OAAO,IAAI,KAAK,CAAC;AAC3B,UAAI,CAAC,OAAO,SAAS,CAAC,GAAG;AACvB,cAAM,IAAI,MAAM,yBAAyB,GAAG,GAAG;AAAA,MACjD;AACA,aAAO;AAAA,IACT;AAAA,IAEA,KAAK,WAAW;AACd,YAAM,IAAI,IAAI,KAAK,EAAE,YAAY;AACjC,UAAI,CAAC,QAAQ,KAAK,OAAO,KAAK,IAAI,EAAE,SAAS,CAAC,EAAG,QAAO;AACxD,UAAI,CAAC,SAAS,KAAK,MAAM,KAAK,KAAK,EAAE,SAAS,CAAC,EAAG,QAAO;AACzD,YAAM,IAAI;AAAA,QACR,yDAAyD,GAAG;AAAA,MAC9D;AAAA,IACF;AAAA,IAEA,KAAK,QAAQ;AACX,UAAI;AACF,eAAO,KAAK,MAAM,GAAG;AAAA,MACvB,QAAQ;AACN,cAAM,IAAI,MAAM,uBAAuB,GAAG,GAAG;AAAA,MAC/C;AAAA,IACF;AAAA,IAEA,KAAK,OAAO;AACV,UAAI;AAGF,YAAI,IAAI,GAAG;AACX,eAAO;AAAA,MACT,QAAQ;AACN,cAAM,IAAI,MAAM,sBAAsB,GAAG,GAAG;AAAA,MAC9C;AAAA,IACF;AAAA;AAAA,IAGA,SAAS;AAEP,YAAM,SAAgB;AACtB,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEO,SAAS,cAAc,aAG5B;AACA,QAAM,WAAW,YAAY,SAAS,GAAG;AACzC,QAAM,OAAO,WAAW,YAAY,MAAM,GAAG,EAAE,IAAI;AAGnD,QAAM,UAAU,CAAC,UAAU,UAAU,WAAW,QAAQ,KAAK;AAC7D,MAAI,CAAC,QAAQ,SAAS,IAAW,GAAG;AAClC,UAAM,IAAI;AAAA,MACR,qBAAqB,WAAW;AAAA,IAClC;AAAA,EACF;AAEA,SAAO,EAAE,UAAU,MAAqB,SAAS;AACnD;;;ACjEO,SAAS,iBACd,QACA,KAC0B;AAC1B,QAAM,SAA2B,CAAC;AAClC,QAAM,MAA+B,CAAC;AAEtC,aAAW,CAAC,KAAK,WAAW,KAAK,OAAO,QAAQ,MAAM,GAAG;AACvD,QAAI;AACJ,QAAI;AAEJ,QAAI;AACF,OAAC,EAAE,UAAU,SAAS,IAAI,cAAc,WAAW;AAAA,IACrD,SAAS,GAAG;AACV,aAAO,KAAK;AAAA,QACV;AAAA,QACA,MAAM;AAAA,QACN,SAAS,OAAO,CAAC;AAAA,MACnB,CAAC;AACD;AAAA,IACF;AAEA,UAAM,MAAM,IAAI,GAAG;AAEnB,QAAI,QAAQ,UAAa,QAAQ,IAAI;AACnC,UAAI,UAAU;AACZ,YAAI,GAAG,IAAI;AAAA,MACb,OAAO;AACL,eAAO,KAAK;AAAA,UACV;AAAA,UACA,MAAM;AAAA,UACN,SAAS;AAAA,QACX,CAAC;AAAA,MACH;AACA;AAAA,IACF;AAEA,QAAI;AACF,UAAI,GAAG,IAAI,YAAY,UAAU,GAAG;AAAA,IACtC,SAAS,GAAG;AACV,aAAO,KAAK;AAAA,QACV;AAAA,QACA,MAAM;AAAA,QACN,SAAS,OAAO,CAAC;AAAA,MACnB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,GAAG;AACrB,UAAM,IAAI,eAAe,MAAM;AAAA,EACjC;AAEA,SAAO;AACT;;;AHlDO,SAAS,UACd,QACA,UAA4B,CAAC,GACH;AAC1B,QAAM,EAAE,aAAa,MAAM,MAAM,QAAQ,IAAI,IAAI;AAEjD,MAAI,WAAY,CAAO,cAAO;AAE9B,SAAO,iBAAiB,QAAQ,GAAG;AACrC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/envDoctor.ts","../src/errors/EnvDoctorError.ts","../src/validators/primitives.ts","../src/core/runner.ts"],"sourcesContent":["export { envDoctor } from \"./core/envDoctor\";\n\nexport type { EnvDoctorOptions, EnvDoctorSchema } from \"./types\";\n\nexport { EnvDoctorError } from \"./errors/EnvDoctorError\";\n","import * as dotenv from \"dotenv\";\nimport { validateAndParse } from \"./runner\";\nimport type { EnvDoctorOptions, EnvDoctorResult, EnvDoctorSchema } from \"../types\";\n\nexport type { EnvDoctorOptions, EnvDoctorSchema } from \"../types\";\nexport { EnvDoctorError } from \"../errors/EnvDoctorError\";\n\nexport function envDoctor<TSchema extends EnvDoctorSchema>(\n schema: TSchema,\n options: EnvDoctorOptions = {}\n): EnvDoctorResult<TSchema> {\n const { loadDotEnv = true, env = process.env } = options;\n\n if (loadDotEnv) dotenv.config();\n\n return validateAndParse(schema, env);\n}\n","export type EnvDoctorIssue =\n | { key: string; kind: \"missing\"; message: string }\n | { key: string; kind: \"invalid\"; message: string };\n\nexport class EnvDoctorError extends Error {\n public readonly issues: EnvDoctorIssue[];\n\n constructor(issues: EnvDoctorIssue[]) {\n const header = \"ENV validation failed\";\n const lines = issues.map((i) => `- ${i.key}: ${i.message}`);\n super([header, ...lines].join(\"\\n\"));\n this.name = \"EnvDoctorError\";\n this.issues = issues;\n }\n}\n","import type { EnvBaseType, EnvSchemaValue } from \"../types\";\n\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\nexport function parseByType(type: EnvBaseType, raw: string): unknown {\n switch (type) {\n case \"string\":\n return raw;\n\n case \"number\": {\n const n = Number(raw.trim());\n if (!Number.isFinite(n)) throw new Error(`expected number, got \"${raw}\"`);\n return n;\n }\n\n case \"boolean\": {\n const v = raw.trim().toLowerCase();\n if ([\"true\", \"1\", \"yes\", \"y\", \"on\"].includes(v)) return true;\n if ([\"false\", \"0\", \"no\", \"n\", \"off\"].includes(v)) return false;\n throw new Error(\n `expected boolean (true/false/1/0/yes/no/on/off), got \"${raw}\"`,\n );\n }\n\n case \"json\": {\n try {\n return JSON.parse(raw);\n } catch {\n throw new Error(`expected json, got \"${raw}\"`);\n }\n }\n\n case \"url\": {\n try {\n new URL(raw);\n return raw;\n } catch {\n throw new Error(`expected url, got \"${raw}\"`);\n }\n }\n\n case \"email\": {\n const s = raw.trim();\n if (!EMAIL_RE.test(s)) {\n throw new Error(`expected email, got \"${raw}\"`);\n }\n return s;\n }\n\n default: {\n throw new Error(`unsupported primitive type: ${String(type)}`);\n }\n }\n}\n\nfunction hasOwn(obj: unknown, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(obj, key);\n}\n\n/**\n * Type-check + normalize a default value into the right runtime type.\n * - Allows string defaults for number/boolean (parsed)\n * - Validates url/email defaults\n * - json defaults can be any value\n */\nfunction coerceDefault(kind: EnvBaseType, def: unknown): unknown {\n if (def === undefined) return undefined;\n\n switch (kind) {\n case \"string\": {\n if (typeof def !== \"string\")\n throw new Error(`default for string must be a string`);\n return def;\n }\n\n case \"number\": {\n if (typeof def === \"number\") {\n if (!Number.isFinite(def))\n throw new Error(`default for number must be finite`);\n return def;\n }\n if (typeof def === \"string\") return parseByType(\"number\", def);\n throw new Error(`default for number must be number or string`);\n }\n\n case \"boolean\": {\n if (typeof def === \"boolean\") return def;\n if (typeof def === \"string\") return parseByType(\"boolean\", def);\n throw new Error(`default for boolean must be boolean or string`);\n }\n\n case \"json\": {\n // allow any JSON-ish value\n return def;\n }\n\n case \"url\": {\n if (typeof def !== \"string\")\n throw new Error(`default for url must be a string`);\n return parseByType(\"url\", def);\n }\n\n case \"email\": {\n if (typeof def !== \"string\")\n throw new Error(`default for email must be a string`);\n return parseByType(\"email\", def);\n }\n\n /* c8 ignore next */\n default: {\n throw new Error(`unsupported default kind: ${String(kind)}`);\n }\n }\n}\n\n/** Normalized spec used by the runner */\nexport type NormalizedSpec =\n | { kind: EnvBaseType; optional: boolean; defaultValue?: unknown }\n | {\n kind: \"enum\";\n optional: boolean;\n values: readonly string[];\n defaultValue?: string;\n }\n | {\n kind: \"regex\";\n optional: boolean;\n re: RegExp;\n display: string;\n defaultValue?: string;\n };\n\nexport function normalizeSpec(schemaValue: EnvSchemaValue): NormalizedSpec {\n // --------------------\n // String style: \"number?\" etc.\n // --------------------\n if (typeof schemaValue === \"string\") {\n const optional = schemaValue.endsWith(\"?\");\n const base = optional ? schemaValue.slice(0, -1) : schemaValue;\n\n const allowed: readonly EnvBaseType[] = [\n \"string\",\n \"number\",\n \"boolean\",\n \"json\",\n \"url\",\n \"email\",\n ];\n\n if (!allowed.includes(base as EnvBaseType)) {\n throw new Error(\n `Unsupported type \"${schemaValue}\". Supported: string, number, boolean, json, url, email (optional with ?)`,\n );\n }\n\n return { kind: base as EnvBaseType, optional };\n }\n\n // --------------------\n // Object style\n // --------------------\n if (\n !schemaValue ||\n typeof schemaValue !== \"object\" ||\n Array.isArray(schemaValue)\n ) {\n throw new Error(\"Schema value must be a string or object spec.\");\n }\n\n const t = (schemaValue as any).type;\n\n // --------------------\n // Primitive object spec: { type: \"number\", optional?: true, default?: ... }\n // --------------------\n const primitiveAllowed: readonly EnvBaseType[] = [\n \"string\",\n \"number\",\n \"boolean\",\n \"json\",\n \"url\",\n \"email\",\n ];\n\n if (primitiveAllowed.includes(t)) {\n const optional = !!(schemaValue as any).optional;\n const defaultValue = hasOwn(schemaValue, \"default\")\n ? coerceDefault(t as EnvBaseType, (schemaValue as any).default)\n : undefined;\n\n return { kind: t as EnvBaseType, optional, defaultValue };\n }\n\n // --------------------\n // Enum: { type: \"enum\", values: [...], optional?: true, default?: \"dev\" }\n // --------------------\n if (t === \"enum\") {\n const values = (schemaValue as any).values;\n if (\n !Array.isArray(values) ||\n values.length === 0 ||\n !values.every((v: any) => typeof v === \"string\")\n ) {\n throw new Error(`enum spec requires \"values\": string[] (non-empty)`);\n }\n\n const optional = !!(schemaValue as any).optional;\n\n let defaultValue: string | undefined = undefined;\n if (hasOwn(schemaValue, \"default\")) {\n const def = (schemaValue as any).default;\n if (typeof def !== \"string\") {\n throw new Error(`default for enum must be a string`);\n }\n if (!values.includes(def)) {\n throw new Error(\n `default \"${def}\" must be one of [${values.join(\", \")}]`,\n );\n }\n defaultValue = def;\n }\n\n return { kind: \"enum\", optional, values, defaultValue };\n }\n\n // --------------------\n // Regex: { type: \"regex\", pattern: \"...\", flags?: \"...\", optional?: true, default?: \"abc\" }\n // --------------------\n if (t === \"regex\") {\n const pattern = (schemaValue as any).pattern;\n const flags = (schemaValue as any).flags;\n\n if (typeof pattern !== \"string\" || pattern.length === 0) {\n throw new Error(`regex spec requires \"pattern\": string`);\n }\n if (flags !== undefined && typeof flags !== \"string\") {\n throw new Error(`regex spec \"flags\" must be a string if provided`);\n }\n\n let re: RegExp;\n try {\n re = new RegExp(pattern, flags);\n } catch (e) {\n throw new Error(`invalid regex: ${String(e)}`);\n }\n\n const display = `/${pattern}/${flags ?? \"\"}`;\n const optional = !!(schemaValue as any).optional;\n\n let defaultValue: string | undefined = undefined;\n if (hasOwn(schemaValue, \"default\")) {\n const def = (schemaValue as any).default;\n if (typeof def !== \"string\") {\n throw new Error(`default for regex must be a string`);\n }\n if (!re.test(def)) {\n throw new Error(`default \"${def}\" does not match ${display}`);\n }\n defaultValue = def;\n }\n\n return { kind: \"regex\", optional, re, display, defaultValue };\n }\n\n throw new Error(\n `Unsupported object spec type \"${String(t)}\". Supported: primitives (string/number/boolean/json/url/email), enum, regex`,\n );\n}\n","import { EnvDoctorError, type EnvDoctorIssue } from \"../errors/EnvDoctorError\";\nimport { normalizeSpec, parseByType } from \"../validators/primitives\";\nimport type { EnvDoctorResult, EnvDoctorSchema } from \"../types\";\n\nexport function validateAndParse<TSchema extends EnvDoctorSchema>(\n schema: TSchema,\n env: Record<string, string | undefined>,\n): EnvDoctorResult<TSchema> {\n const issues: EnvDoctorIssue[] = [];\n const out: Record<string, unknown> = {};\n\n for (const [key, schemaValue] of Object.entries(schema)) {\n let spec: ReturnType<typeof normalizeSpec>;\n\n try {\n spec = normalizeSpec(schemaValue);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n issues.push({ key, kind: \"invalid\", message: msg });\n continue;\n }\n\n const raw = env[key];\n\n // treat undefined or empty string as \"missing\"\n if (raw === undefined || raw === \"\") {\n if (spec.defaultValue !== undefined) {\n out[key] = spec.defaultValue;\n } else if (spec.optional) {\n out[key] = undefined;\n } else {\n issues.push({\n key,\n kind: \"missing\",\n message: \"missing required environment variable\",\n });\n }\n continue;\n }\n\n try {\n if (spec.kind === \"enum\") {\n if (!spec.values.includes(raw)) {\n throw new Error(\n `expected one of [${spec.values.join(\", \")}], got \"${raw}\"`,\n );\n }\n out[key] = raw;\n } else if (spec.kind === \"regex\") {\n if (!spec.re.test(raw)) {\n throw new Error(`does not match ${spec.display}`);\n }\n out[key] = raw;\n } else {\n // primitives: string/number/boolean/json/url/email\n out[key] = parseByType(spec.kind, raw);\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n issues.push({ key, kind: \"invalid\", message: msg });\n }\n }\n\n if (issues.length > 0) throw new EnvDoctorError(issues);\n\n return out as EnvDoctorResult<TSchema>;\n}\n"],"mappings":"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","names":[]}
|