import { randomBytes } from "crypto"; import type { EnvVariable, GenerateOptions } from "./types"; // Secret Generation // ============================================================================ /** * Generate a secure random secret */ export function generateSecret(length: number = 32): string { return randomBytes(Math.ceil(length / 2)) .toString('hex') .slice(0, length); } // ============================================================================ // File Generation // ============================================================================ /** * Generate .env file content */ export function generateEnvFile( variables: EnvVariable[], environment: string, options: GenerateOptions ): string { const lines: string[] = []; // Header if (environment === 'example') { lines.push('# Environment Configuration Template'); lines.push('# Copy this file to .env.local and fill in the values'); } else { lines.push(`# ${environment.charAt(0).toUpperCase() + environment.slice(1)} Environment Configuration`); } lines.push(`# Generated: ${new Date().toISOString()}`); lines.push(''); // Group by category const categories = new Map(); variables.forEach(v => { const cat = v.category; if (!categories.has(cat)) categories.set(cat, []); categories.get(cat)!.push(v); }); // Generate sections for (const [category, vars] of categories) { lines.push('# ' + '='.repeat(77)); lines.push(`# ${category.toUpperCase()}`); lines.push('# ' + '='.repeat(77)); lines.push(''); for (const envVar of vars) { // Add description if (options.format !== 'minimal') { lines.push(`# ${envVar.description}`); if (options.includeExamples && envVar.example) { lines.push(`# Example: ${envVar.example}`); } if (options.securityWarnings && envVar.sensitive) { lines.push('# ⚠️ SECURITY WARNING: This is a sensitive credential'); lines.push('# - Never commit this to version control'); lines.push('# - Use a strong, randomly generated value'); lines.push('# - Store securely (e.g., password manager, secrets vault)'); } } // Generate value let value = ''; if (environment === 'example') { value = envVar.example || 'your-value-here'; } else if (options.generateSecrets && envVar.sensitive && !envVar.example?.includes('sk-')) { value = generateSecret(options.secretLength); } else { value = envVar.defaultValue || envVar.example || ''; } // Add variable if (environment === 'example' || !value) { lines.push(`${envVar.name}="${value}"`); } else { lines.push(`${envVar.name}="${value}"`); } lines.push(''); } lines.push(''); } return lines.join('\n'); } /** * Generate TypeScript type definitions */ export function generateTypeDefs(variables: EnvVariable[]): string { const lines: string[] = []; lines.push('/**'); lines.push(' * Environment Variables Type Definitions'); lines.push(' * Generated by generate-env skill'); lines.push(' */'); lines.push(''); lines.push('declare global {'); lines.push(' namespace NodeJS {'); lines.push(' interface ProcessEnv {'); // Group by category const categories = new Map(); variables.forEach(v => { const cat = v.category; if (!categories.has(cat)) categories.set(cat, []); categories.get(cat)!.push(v); }); for (const [category, vars] of categories) { lines.push(` // ${category.charAt(0).toUpperCase() + category.slice(1)}`); for (const envVar of vars) { let type = 'string'; if (envVar.name === 'NODE_ENV') { type = "'development' | 'production' | 'test'"; } else if (envVar.name === 'PORT') { type = 'string'; } const optional = envVar.required ? '' : '?'; lines.push(` ${envVar.name}${optional}: ${type};`); } lines.push(''); } lines.push(' }'); lines.push(' }'); lines.push('}'); lines.push(''); lines.push('export {};'); return lines.join('\n'); } /** * Generate Zod validation schema */ export function generateValidation(variables: EnvVariable[]): string { const lines: string[] = []; lines.push("import { z } from 'zod';"); lines.push(''); lines.push('/**'); lines.push(' * Environment Variables Validation Schema'); lines.push(' * Generated by generate-env skill'); lines.push(' */'); lines.push(''); lines.push('export const envSchema = z.object({'); // Group by category const categories = new Map(); variables.forEach(v => { const cat = v.category; if (!categories.has(cat)) categories.set(cat, []); categories.get(cat)!.push(v); }); for (const [category, vars] of categories) { lines.push(` // ${category.charAt(0).toUpperCase() + category.slice(1)}`); for (const envVar of vars) { let validation = envVar.validation || 'z.string()'; // Add specific validations if (envVar.name.endsWith('_URL')) { validation = 'z.string().url()'; } else if (envVar.name.endsWith('_EMAIL')) { validation = 'z.string().email()'; } else if (envVar.sensitive && !envVar.validation) { validation = 'z.string().min(1)'; } if (!envVar.required) { validation += '.optional()'; } lines.push(` ${envVar.name}: ${validation},`); } lines.push(''); } lines.push('});'); lines.push(''); lines.push('export type Env = z.infer;'); lines.push(''); lines.push('/**'); lines.push(' * Validate environment variables at runtime'); lines.push(' */'); lines.push('export function validateEnv(): Env {'); lines.push(' const parsed = envSchema.safeParse(process.env);'); lines.push(''); lines.push(' if (!parsed.success) {'); lines.push(" console.error('❌ Invalid environment variables:', parsed.error.flatten().fieldErrors);"); lines.push(" throw new Error('Invalid environment variables');"); lines.push(' }'); lines.push(''); lines.push(' return parsed.data;'); lines.push('}'); return lines.join('\n'); } /** * Generate metadata file */ export function generateMetadata(variables: EnvVariable[], options: GenerateOptions): string { return JSON.stringify({ generatedAt: new Date().toISOString(), totalVariables: variables.length, categories: Array.from(new Set(variables.map(v => v.category))).sort(), sensitiveCount: variables.filter(v => v.sensitive).length, requiredCount: variables.filter(v => v.required).length, options: { templateOnly: options.templateOnly, generateSecrets: options.generateSecrets, withTypes: options.withTypes, withValidation: options.withValidation, }, variables: variables.map(v => ({ name: v.name, category: v.category, required: v.required, sensitive: v.sensitive, })), }, null, 2); }