env-sentinel
Version:
Zero-dependency tool that auto-validates .env files against schema.env, with optional fallback and secure warnings.
89 lines (88 loc) • 3.17 kB
JavaScript
import { existsSync, readFileSync } from 'node:fs';
import { inferType } from './infer-type.js';
export function generateSchemaFromEnv(envPath, lintResults) {
if (!existsSync(envPath)) {
throw new Error(`File not found: ${envPath}`);
}
const raw = readFileSync(envPath, 'utf-8');
const lines = raw.split(/\r?\n/);
const schemaEntries = [];
const errorLines = new Set();
if (lintResults) {
lintResults.issues
.filter((issue) => issue.severity === 'error')
.forEach((issue) => {
if (issue.line) {
errorLines.add(issue.line);
}
});
}
const envVars = new Map();
lines.forEach((line, lineIndex) => {
const lineNumber = lineIndex + 1;
const trimmedLine = line.trim();
if (!trimmedLine || trimmedLine.startsWith('#')) {
return;
}
if (errorLines.has(lineNumber)) {
return;
}
const equalIndex = trimmedLine.indexOf('=');
if (equalIndex === -1) {
return;
}
const key = trimmedLine.substring(0, equalIndex).trim();
const value = trimmedLine.substring(equalIndex + 1).trim();
if (!key || key.includes(' ') || key.includes('"') || key.includes("'")) {
return;
}
let cleanValue = value;
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
cleanValue = value.slice(1, -1);
}
if (cleanValue === '') {
return;
}
envVars.set(key, cleanValue);
});
lines.forEach((line, lineIndex) => {
const lineNumber = lineIndex + 1;
const trimmedLine = line.trim();
if (!trimmedLine || trimmedLine.startsWith('#')) {
return;
}
if (errorLines.has(lineNumber)) {
return;
}
const equalIndex = trimmedLine.indexOf('=');
if (equalIndex === -1) {
return;
}
const key = trimmedLine.substring(0, equalIndex).trim();
const value = trimmedLine.substring(equalIndex + 1).trim();
if (!key || key.includes(' ') || key.includes('"') || key.includes("'")) {
return;
}
let cleanValue = value;
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
cleanValue = value.slice(1, -1);
}
if (cleanValue === '') {
return;
}
let resolvedValue = cleanValue;
if (cleanValue.startsWith('$') && cleanValue.length > 1) {
const refKey = cleanValue.substring(1);
const refValue = envVars.get(refKey);
if (refValue !== undefined) {
resolvedValue = refValue;
}
}
const type = inferType(resolvedValue);
const rule = type ? `required|${type}` : 'required';
schemaEntries.push({ key, rule });
});
return (`# Generated from ${envPath}\n` +
schemaEntries.map((entry) => `${entry.key}=${entry.rule}`).join('\n') +
'\n');
}