env-schema-checker
Version:
A zero-config, type-safe, schema-driven .env validator with automatic error reporting, autocomplete, and runtime checks
138 lines (136 loc) • 3.91 kB
JavaScript
import { config } from 'dotenv';
import { z } from 'zod';
// src/index.ts
function createZodSchema(schema) {
const zodSchema = {};
for (const [key, value] of Object.entries(schema)) {
if (value instanceof z.ZodType) {
zodSchema[key] = value;
} else if (Array.isArray(value)) {
zodSchema[key] = z.enum(value);
} else {
switch (value) {
case "string":
zodSchema[key] = z.string();
break;
case "number":
zodSchema[key] = z.string().refine((val) => !isNaN(Number(val)), {
message: "Invalid number format"
}).transform((val) => Number(val));
break;
case "boolean":
zodSchema[key] = z.enum(["true", "false"]).transform((val) => val === "true");
break;
case "array":
zodSchema[key] = z.string().refine((val) => {
try {
const parsed = JSON.parse(val);
return Array.isArray(parsed);
} catch (e) {
return val.includes(",");
}
}, { message: "Must be a JSON array or comma-separated values" }).transform((val) => {
try {
const parsed = JSON.parse(val);
if (Array.isArray(parsed)) return parsed;
} catch (e) {
}
return val.split(",");
});
break;
case "object":
zodSchema[key] = z.string().transform((val) => {
const parsed = JSON.parse(val);
if (typeof parsed !== "object" || Array.isArray(parsed) || parsed === null) {
throw new Error("Must be a JSON object");
}
return parsed;
});
break;
default:
zodSchema[key] = z.string();
}
}
}
return z.object(zodSchema);
}
function loadEnv(options) {
const { schema, path, encoding, override, debug } = options;
if (path) {
try {
const result = config({ path, encoding, override, debug });
if (result.error) {
throw result.error;
}
} catch (error) {
const nodeError = error;
if (nodeError.code !== "ENOENT") {
throw error;
}
}
}
const zodSchema = createZodSchema(schema);
const validationResult = zodSchema.safeParse(process.env);
if (!validationResult.success) {
const errors = validationResult.error.errors.map((err) => ({
key: err.path.join("."),
message: err.message,
value: process.env[err.path[0]]
}));
return {
success: false,
errors,
env: {}
};
}
return {
success: true,
env: validationResult.data
};
}
function generateTypes(options) {
const { schema, outputPath } = options;
let typeDefinitions = "declare global {\n namespace NodeJS {\n interface ProcessEnv {\n";
for (const [key, value] of Object.entries(schema)) {
let type;
if (value instanceof z.ZodType) {
type = "string";
} else if (Array.isArray(value)) {
type = value.map((v) => `'${v}'`).join(" | ");
} else {
switch (value) {
case "number":
type = "number";
break;
case "boolean":
type = "boolean";
break;
case "array":
type = "string[]";
break;
case "object":
type = "Record<string, unknown>";
break;
default:
type = "string";
}
}
typeDefinitions += ` ${key}: ${type};
`;
}
typeDefinitions += " }\n }\n}\n\nexport {};";
return typeDefinitions;
}
function validateEnv(schema) {
return loadEnv({ schema });
}
function parseEnvFile(path) {
const result = config({ path });
if (result.error) {
throw result.error;
}
return result.parsed || {};
}
export { generateTypes, loadEnv, parseEnvFile, validateEnv };
//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map