env-schema-checker
Version:
A zero-config, type-safe, schema-driven .env validator with automatic error reporting, autocomplete, and runtime checks
229 lines (225 loc) • 7.66 kB
JavaScript
;
var commander = require('commander');
var dotenv = require('dotenv');
var zod = require('zod');
var fs = require('fs');
var path = require('path');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var fs__default = /*#__PURE__*/_interopDefault(fs);
var path__default = /*#__PURE__*/_interopDefault(path);
function createZodSchema(schema) {
const zodSchema = {};
for (const [key, value] of Object.entries(schema)) {
if (value instanceof zod.z.ZodType) {
zodSchema[key] = value;
} else if (Array.isArray(value)) {
zodSchema[key] = zod.z.enum(value);
} else {
switch (value) {
case "string":
zodSchema[key] = zod.z.string();
break;
case "number":
zodSchema[key] = zod.z.string().refine((val) => !isNaN(Number(val)), {
message: "Invalid number format"
}).transform((val) => Number(val));
break;
case "boolean":
zodSchema[key] = zod.z.enum(["true", "false"]).transform((val) => val === "true");
break;
case "array":
zodSchema[key] = zod.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] = zod.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] = zod.z.string();
}
}
}
return zod.z.object(zodSchema);
}
function loadEnv(options) {
const { schema, path: path2, encoding, override, debug } = options;
if (path2) {
try {
const result = dotenv.config({ path: path2, 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} = 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 zod.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;
}
var program = new commander.Command();
program.name("env-schema-checker").description("A zero-config, type-safe, schema-driven .env validator").version("0.1.1");
program.command("validate").description("Validate environment variables against a schema").option("-p, --path <path>", "Path to .env file").option("-s, --schema <path>", "Path to schema file (JSON)").action(async (options) => {
var _a;
try {
const schemaPath = options.schema || ".env.schema.json";
if (!fs__default.default.existsSync(schemaPath)) {
console.error(`Schema file not found: ${schemaPath}`);
process.exit(1);
}
const schema = JSON.parse(fs__default.default.readFileSync(schemaPath, "utf-8"));
const result = loadEnv({
schema,
path: options.path || ".env"
});
if (!result.success) {
console.error("Environment validation failed:");
(_a = result.errors) == null ? void 0 : _a.forEach((err) => {
console.error(` ${err.key}: ${err.message} (received: ${err.value})`);
});
process.exit(1);
}
console.log("\u2705 Environment validation successful!");
} catch (error) {
if (error instanceof Error) {
console.error("Error:", error.message);
} else {
console.error("An unknown error occurred");
}
process.exit(1);
}
});
program.command("gen-types").description("Generate TypeScript type definitions from schema").option("-s, --schema <path>", "Path to schema file (JSON)").option("-o, --output <path>", "Output path for type definitions").action(async (options) => {
try {
const schemaPath = options.schema || ".env.schema.json";
if (!fs__default.default.existsSync(schemaPath)) {
console.error(`Schema file not found: ${schemaPath}`);
process.exit(1);
}
const schema = JSON.parse(fs__default.default.readFileSync(schemaPath, "utf-8"));
const types = generateTypes({
schema,
outputPath: options.output
});
if (options.output) {
const outputDir = path__default.default.dirname(options.output);
if (!fs__default.default.existsSync(outputDir)) {
fs__default.default.mkdirSync(outputDir, { recursive: true });
}
fs__default.default.writeFileSync(options.output, types);
console.log(`\u2705 Type definitions written to ${options.output}`);
} else {
console.log(types);
}
} catch (error) {
if (error instanceof Error) {
console.error("Error:", error.message);
} else {
console.error("An unknown error occurred");
}
process.exit(1);
}
});
program.command("init").description("Initialize a new schema file").option("-o, --output <path>", "Output path for schema file", ".env.schema.json").action(async (options) => {
try {
const defaultSchema = {
NODE_ENV: ["development", "production", "test"],
PORT: "number",
HOST: "string",
DEBUG: "boolean"
};
const outputDir = path__default.default.dirname(options.output);
if (!fs__default.default.existsSync(outputDir)) {
fs__default.default.mkdirSync(outputDir, { recursive: true });
}
fs__default.default.writeFileSync(
options.output,
JSON.stringify(defaultSchema, null, 2) + "\n"
);
console.log(`\u2705 Schema file created at ${options.output}`);
console.log("\nExample usage:");
console.log(" $ env-schema-checker validate");
console.log(" $ env-schema-checker gen-types -o types/env.d.ts");
} catch (error) {
if (error instanceof Error) {
console.error("Error:", error.message);
} else {
console.error("An unknown error occurred");
}
process.exit(1);
}
});
program.parse(process.argv);
//# sourceMappingURL=cli.js.map
//# sourceMappingURL=cli.js.map