#!/usr/bin/env bun /** * Generate Configuration Files from Unified Config * * This script reads the unified configuration and generates all necessary * config files for different environments and deployment targets. * * Usage: * bun scripts/generate-unified-config.ts [environment] * bun scripts/generate-unified-config.ts production * bun scripts/generate-unified-config.ts --all */ import { writeFileSync, mkdirSync, existsSync } from 'fs'; import { join, dirname } from 'path'; import { createUnifiedConfigForEnvironment, exportAsEnvVars, type Environment, type UnifiedConfig, } from '../config/unified.config'; interface GeneratorOptions { environment?: Environment; outputDir?: string; generateAll?: boolean; dryRun?: boolean; verbose?: boolean; } class ConfigGenerator { private options: GeneratorOptions; private projectRoot: string; constructor(options: GeneratorOptions = {}) { this.options = options; this.projectRoot = join(__dirname, '..'); } /** * Generate all configuration files */ async generate(): Promise { const environments: Environment[] = this.options.generateAll ? ['development', 'staging', 'production'] : [this.options.environment || this.getCurrentEnvironment()]; console.warn('šŸ”§ Generating unified configuration files...'); console.warn(`šŸ“ Project root: ${this.projectRoot}`); console.warn(`šŸŒ Environments: ${environments.join(', ')}`); for (const env of environments) { await this.generateForEnvironment(env); } console.warn('āœ… Configuration generation completed!'); } /** * Generate configuration files for a specific environment */ private async generateForEnvironment( environment: Environment ): Promise { console.warn(`\nšŸ”„ Generating configuration for: ${environment}`); // Create config instance for specific environment const config = createUnifiedConfigForEnvironment(environment); const envVars = exportAsEnvVars(config); // Generate different config file formats await Promise.all([ this.generateDotEnv(environment, envVars), this.generateKubernetesConfig(environment, config, envVars), this.generateDockerEnv(environment, envVars), this.generateViteEnv(environment, envVars), this.generateTypeScriptConfig(environment, config), ]); console.warn(`āœ… Generated configuration for ${environment}`); } /** * Generate .env file */ private async generateDotEnv( environment: Environment, envVars: Record ): Promise { const content = this.createEnvFileContent(environment, envVars); const filePath = join(this.projectRoot, `.env.${environment}`); this.writeFile(filePath, content, `šŸ“„ .env.${environment}`); } /** * Generate Kubernetes config.env file */ private async generateKubernetesConfig( environment: Environment, config: UnifiedConfig, envVars: Record ): Promise { const overlayDir = join( this.projectRoot, 'k8s-kustomize', 'overlays', environment ); this.ensureDirectoryExists(overlayDir); // Generate config.env for kustomize const configContent = this.createKubernetesConfigContent( environment, envVars ); const configPath = join(overlayDir, 'config.env'); this.writeFile( configPath, configContent, `🚢 Kubernetes config.env (${environment})` ); // Generate kustomization.yaml updates await this.updateKustomizationFile(environment, config); } /** * Generate Docker environment file */ private async generateDockerEnv( environment: Environment, envVars: Record ): Promise { const dockerDir = join(this.projectRoot, 'docker'); this.ensureDirectoryExists(dockerDir); const content = this.createDockerEnvContent(environment, envVars); const filePath = join(dockerDir, `.env.${environment}`); this.writeFile(filePath, content, `🐳 Docker .env.${environment}`); } /** * Generate Vite-specific environment file */ private async generateViteEnv( environment: Environment, envVars: Record ): Promise { const viteVars = Object.entries(envVars) .filter(([key]) => key.startsWith('VITE_') || this.isViteRelevant(key)) .reduce( (acc, [key, value]) => { acc[key.startsWith('VITE_') ? key : `VITE_${key}`] = value; return acc; }, {} as Record ); const content = this.createViteEnvContent(environment, viteVars); const filePath = join(this.projectRoot, `.env.vite.${environment}`); this.writeFile(filePath, content, `⚔ Vite .env.vite.${environment}`); } /** * Generate TypeScript config export */ private async generateTypeScriptConfig( environment: Environment, config: UnifiedConfig ): Promise { const configDir = join(this.projectRoot, 'config', 'generated'); this.ensureDirectoryExists(configDir); const content = this.createTypeScriptConfigContent(environment, config); const filePath = join(configDir, `${environment}.config.ts`); this.writeFile(filePath, content, `šŸ“ TypeScript ${environment}.config.ts`); } /** * Create .env file content */ private createEnvFileContent( environment: Environment, envVars: Record ): string { const timestamp = new Date().toISOString(); return `# ${environment.toUpperCase()} Environment Configuration # Generated automatically from unified configuration # Generated on: ${timestamp} # # This file was auto-generated. Do not edit manually. # To make changes, update config/unified.config.ts and regenerate. ${Object.entries(envVars) .map(([key, value]) => `${key}=${value}`) .join('\n')} # Additional environment-specific variables can be added below # (These will not be overwritten during regeneration) `; } /** * Create Kubernetes config.env content */ private createKubernetesConfigContent( environment: Environment, envVars: Record ): string { const timestamp = new Date().toISOString(); // Filter to Kubernetes-relevant variables const k8sVars = Object.entries(envVars) .filter( ([key]) => !key.startsWith('VITE_') && this.isKubernetesRelevant(key) ) .reduce( (acc, [key, value]) => { acc[key] = value; return acc; }, {} as Record ); return `# Kubernetes configuration for ${environment} # Generated automatically from unified configuration # Generated on: ${timestamp} ${Object.entries(k8sVars) .map(([key, value]) => `${key}=${value}`) .join('\n')} `; } /** * Create Docker environment content */ private createDockerEnvContent( environment: Environment, envVars: Record ): string { const timestamp = new Date().toISOString(); return `# Docker environment for ${environment} # Generated on: ${timestamp} # Container Configuration DOCKER_IMAGE=${envVars.DOCKER_IMAGE} CONTAINER_REGISTRY=${envVars.CONTAINER_REGISTRY} CONTAINER_REPOSITORY=${envVars.CONTAINER_REPOSITORY} CONTAINER_TAG=${envVars.CONTAINER_TAG} # Application Configuration APP_NAME=${envVars.APP_NAME} NODE_ENV=${envVars.NODE_ENV} PORT=${envVars.PORT} # Database Configuration COUCHDB_URL=${envVars.COUCHDB_URL} COUCHDB_USER=${envVars.COUCHDB_USER} COUCHDB_PASSWORD=${envVars.COUCHDB_PASSWORD} `; } /** * Create Vite environment content */ private createViteEnvContent( environment: Environment, viteVars: Record ): string { const timestamp = new Date().toISOString(); return `# Vite environment variables for ${environment} # Generated on: ${timestamp} # # These variables are available in the frontend build process ${Object.entries(viteVars) .map(([key, value]) => `${key}=${value}`) .join('\n')} `; } /** * Create TypeScript config content */ private createTypeScriptConfigContent( environment: Environment, config: UnifiedConfig ): string { const timestamp = new Date().toISOString(); return `/** * Generated configuration for ${environment} * Generated on: ${timestamp} * * This file exports the resolved configuration for the ${environment} environment. * It can be imported by other TypeScript files for type-safe configuration access. */ import type { UnifiedConfig } from '../unified.config'; export const ${environment}Config: UnifiedConfig = ${JSON.stringify(config, null, 2)} as const; export default ${environment}Config; `; } /** * Update kustomization.yaml file with current configuration */ private async updateKustomizationFile( environment: Environment, config: UnifiedConfig ): Promise { const overlayDir = join( this.projectRoot, 'k8s-kustomize', 'overlays', environment ); const kustomizationPath = join(overlayDir, 'kustomization.yaml'); if (!existsSync(kustomizationPath)) { // Create new kustomization.yaml const content = this.createKustomizationContent(environment, config); this.writeFile( kustomizationPath, content, `šŸŽ›ļø kustomization.yaml (${environment})` ); } else { console.warn( ` ā„¹ļø Kustomization file exists: ${kustomizationPath} (not overwriting)` ); } } /** * Create kustomization.yaml content */ private createKustomizationContent( environment: Environment, config: UnifiedConfig ): string { return `apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization metadata: name: ${config.app.name}-${environment} # Reference the base configuration resources: - ../../base - namespace.yaml # Override namespace for ${environment} namespace: ${config.kubernetes.namespace} # ${environment.charAt(0).toUpperCase() + environment.slice(1)}-specific labels labels: - pairs: environment: ${environment} tier: ${environment === 'production' ? 'prod' : environment} # ${environment.charAt(0).toUpperCase() + environment.slice(1)} image tags and configurations images: - name: frontend-image newName: ${config.container.registry}/${config.container.repository} newTag: ${config.container.tag} - name: couchdb-image newName: couchdb newTag: 3.3.2 # ${environment.charAt(0).toUpperCase() + environment.slice(1)} replicas replicas: - name: ${config.app.name}-frontend count: ${config.kubernetes.replicas.frontend} - name: ${config.app.name}-couchdb count: ${config.kubernetes.replicas.database} # Environment-specific patches patches: # Resource limits - target: kind: Deployment name: ${config.app.name}-frontend patch: |- - op: replace path: /spec/template/spec/containers/0/resources value: requests: memory: "${config.kubernetes.resources.frontend.requests.memory}" cpu: "${config.kubernetes.resources.frontend.requests.cpu}" limits: memory: "${config.kubernetes.resources.frontend.limits.memory}" cpu: "${config.kubernetes.resources.frontend.limits.cpu}" - target: kind: StatefulSet name: ${config.app.name}-couchdb patch: |- - op: replace path: /spec/template/spec/containers/0/resources value: requests: memory: "${config.kubernetes.resources.database.requests.memory}" cpu: "${config.kubernetes.resources.database.requests.cpu}" limits: memory: "${config.kubernetes.resources.database.limits.memory}" cpu: "${config.kubernetes.resources.database.limits.cpu}" # ConfigMap generation configMapGenerator: - name: ${config.app.name}-config envs: - config.env behavior: create `; } /** * Check if a variable is relevant for Vite */ private isViteRelevant(key: string): boolean { const viteRelevantKeys = [ 'APP_NAME', 'APP_VERSION', 'APP_BASE_URL', 'COUCHDB_URL', 'COUCHDB_USER', 'COUCHDB_PASSWORD', 'MAILGUN_API_KEY', 'MAILGUN_DOMAIN', 'MAILGUN_FROM_NAME', 'MAILGUN_FROM_EMAIL', 'GOOGLE_CLIENT_ID', 'GITHUB_CLIENT_ID', 'ENABLE_EMAIL_VERIFICATION', 'ENABLE_OAUTH', 'ENABLE_ADMIN_INTERFACE', 'DEBUG_MODE', ]; return viteRelevantKeys.includes(key); } /** * Check if a variable is relevant for Kubernetes */ private isKubernetesRelevant(key: string): boolean { const k8sIrrelevantKeys = ['VITE_', 'CONTAINER_', 'DOCKER_']; return !k8sIrrelevantKeys.some(prefix => key.startsWith(prefix)); } /** * Get current environment from NODE_ENV */ private getCurrentEnvironment(): Environment { const env = process.env.NODE_ENV || 'development'; return ['development', 'staging', 'production', 'test'].includes(env) ? (env as Environment) : 'development'; } /** * Ensure directory exists */ private ensureDirectoryExists(dirPath: string): void { if (!existsSync(dirPath)) { mkdirSync(dirPath, { recursive: true }); if (this.options.verbose) { console.warn(` šŸ“ Created directory: ${dirPath}`); } } } /** * Write file with logging */ private writeFile( filePath: string, content: string, description: string ): void { if (this.options.dryRun) { console.warn(` šŸ” [DRY RUN] Would write: ${description}`); console.warn(` Path: ${filePath}`); return; } // Ensure directory exists this.ensureDirectoryExists(dirname(filePath)); writeFileSync(filePath, content, 'utf8'); console.warn(` āœ… Generated: ${description}`); if (this.options.verbose) { console.warn(` Path: ${filePath}`); console.warn(` Size: ${content.length} bytes`); } } } /** * CLI Interface */ async function main() { const args = process.argv.slice(2); const options: GeneratorOptions = {}; // Parse command line arguments for (let i = 0; i < args.length; i++) { const arg = args[i]; switch (arg) { case '--all': options.generateAll = true; break; case '--dry-run': options.dryRun = true; break; case '--verbose': case '-v': options.verbose = true; break; case '--help': case '-h': showHelp(); process.exit(0); break; default: if (!arg.startsWith('--') && !options.environment) { options.environment = arg as Environment; } break; } } // Validate environment if ( options.environment && !['development', 'staging', 'production', 'test'].includes( options.environment ) ) { console.error(`āŒ Invalid environment: ${options.environment}`); console.error( ' Valid environments: development, staging, production, test' ); process.exit(1); } try { const generator = new ConfigGenerator(options); await generator.generate(); } catch (error) { console.error('āŒ Configuration generation failed:', error); process.exit(1); } } /** * Show help message */ function showHelp() { console.warn(` šŸ”§ Unified Configuration Generator USAGE: bun scripts/generate-unified-config.ts [environment] [options] ARGUMENTS: environment Target environment (development, staging, production, test) OPTIONS: --all Generate configuration for all environments --dry-run Show what would be generated without writing files --verbose, -v Show detailed output --help, -h Show this help message EXAMPLES: bun scripts/generate-unified-config.ts development bun scripts/generate-unified-config.ts production --verbose bun scripts/generate-unified-config.ts --all bun scripts/generate-unified-config.ts --dry-run GENERATED FILES: šŸ“„ .env.[environment] - Environment variables 🚢 k8s-kustomize/overlays/[env]/config.env - Kubernetes configuration 🐳 docker/.env.[environment] - Docker environment ⚔ .env.vite.[environment] - Vite-specific variables šŸ“ config/generated/[env].config.ts - TypeScript config export The generator reads from config/unified.config.ts and creates all necessary configuration files for the specified environment(s). `); } // Run CLI if this file is executed directly if (import.meta.main) { main().catch(console.error); } export { ConfigGenerator, type GeneratorOptions };