#!/usr/bin/env node /** * Environment Variable Generator * * Reads from .env.development and generates app-specific .env files * with the appropriate prefixes for each platform. * * Usage: pnpm setup:env */ import { readFileSync, writeFileSync, existsSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT_DIR = join(__dirname, '..'); const ENV_FILE = join(ROOT_DIR, '.env.development'); // Parse a .env file into an object function parseEnvFile(content) { const env = {}; const lines = content.split('\n'); for (const line of lines) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const match = trimmed.match(/^([^=]+)=(.*)$/); if (match) { let [, key, value] = match; // Handle quoted values if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { value = value.slice(1, -1); } env[key.trim()] = value; } } return env; } // Generate env file content function generateEnvContent(vars) { const lines = ['# Auto-generated by scripts/generate-env.mjs', '# Source: .env.development', '']; for (const [key, value] of Object.entries(vars)) { // Quote values that contain special characters or newlines const needsQuotes = value.includes('\n') || value.includes(' ') || value.includes('#'); const formattedValue = needsQuotes ? `"${value}"` : value; lines.push(`${key}=${formattedValue}`); } return lines.join('\n') + '\n'; } // App configurations - maps source variables to target variables const APP_CONFIGS = [ // Mana Core Auth Service { path: 'services/mana-core-auth/.env', vars: { NODE_ENV: () => 'development', PORT: (env) => env.MANA_CORE_AUTH_PORT || '3001', DATABASE_URL: (env) => env.MANA_CORE_AUTH_DATABASE_URL, REDIS_HOST: (env) => env.REDIS_HOST, REDIS_PORT: (env) => env.REDIS_PORT, REDIS_PASSWORD: (env) => env.REDIS_PASSWORD || '', JWT_PRIVATE_KEY: (env) => env.JWT_PRIVATE_KEY, JWT_PUBLIC_KEY: (env) => env.JWT_PUBLIC_KEY, JWT_ACCESS_TOKEN_EXPIRY: (env) => env.JWT_ACCESS_TOKEN_EXPIRY, JWT_REFRESH_TOKEN_EXPIRY: (env) => env.JWT_REFRESH_TOKEN_EXPIRY, JWT_ISSUER: (env) => env.JWT_ISSUER, JWT_AUDIENCE: (env) => env.JWT_AUDIENCE, STRIPE_SECRET_KEY: (env) => env.STRIPE_SECRET_KEY, STRIPE_PUBLISHABLE_KEY: (env) => env.STRIPE_PUBLISHABLE_KEY, STRIPE_WEBHOOK_SECRET: (env) => env.STRIPE_WEBHOOK_SECRET, CORS_ORIGINS: (env) => env.CORS_ORIGINS, CREDITS_SIGNUP_BONUS: (env) => env.CREDITS_SIGNUP_BONUS, CREDITS_DAILY_FREE: (env) => env.CREDITS_DAILY_FREE, RATE_LIMIT_TTL: (env) => env.RATE_LIMIT_TTL, RATE_LIMIT_MAX: (env) => env.RATE_LIMIT_MAX, }, }, // Chat Backend { path: 'apps/chat/apps/backend/.env', vars: { NODE_ENV: () => 'development', PORT: (env) => env.CHAT_BACKEND_PORT || '3002', AZURE_OPENAI_ENDPOINT: (env) => env.AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY: (env) => env.AZURE_OPENAI_API_KEY, AZURE_OPENAI_API_VERSION: (env) => env.AZURE_OPENAI_API_VERSION, MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL, DATABASE_URL: (env) => env.CHAT_DATABASE_URL, }, }, // Chat Mobile (Expo) { path: 'apps/chat/apps/mobile/.env', vars: { EXPO_PUBLIC_MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL, EXPO_PUBLIC_SUPABASE_URL: (env) => env.CHAT_SUPABASE_URL, EXPO_PUBLIC_SUPABASE_ANON_KEY: (env) => env.CHAT_SUPABASE_ANON_KEY, EXPO_PUBLIC_BACKEND_URL: (env) => `http://localhost:${env.CHAT_BACKEND_PORT || '3002'}`, }, }, // Chat Web (SvelteKit) { path: 'apps/chat/apps/web/.env', vars: { PUBLIC_MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL, PUBLIC_SUPABASE_URL: (env) => env.CHAT_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY: (env) => env.CHAT_SUPABASE_ANON_KEY, PUBLIC_BACKEND_URL: (env) => `http://localhost:${env.CHAT_BACKEND_PORT || '3002'}`, }, }, // Maerchenzauber Backend { path: 'apps/maerchenzauber/apps/backend/.env', vars: { NODE_ENV: () => 'development', PORT: (env) => env.MAERCHENZAUBER_BACKEND_PORT || '3003', MANA_SERVICE_URL: (env) => env.MANA_CORE_AUTH_URL, APP_ID: (env) => env.MAERCHENZAUBER_APP_ID, MAERCHENZAUBER_SUPABASE_URL: (env) => env.MAERCHENZAUBER_SUPABASE_URL, MAERCHENZAUBER_SUPABASE_ANON_KEY: (env) => env.MAERCHENZAUBER_SUPABASE_ANON_KEY, MAERCHENZAUBER_JWT_SECRET: (env) => env.MAERCHENZAUBER_JWT_SECRET, MAERCHENZAUBER_AZURE_OPENAI_KEY: (env) => env.MAERCHENZAUBER_AZURE_OPENAI_KEY, MAERCHENZAUBER_AZURE_OPENAI_ENDPOINT: (env) => env.MAERCHENZAUBER_AZURE_OPENAI_ENDPOINT, MAERCHENZAUBER_REPLICATE_API_KEY: (env) => env.MAERCHENZAUBER_REPLICATE_API_KEY, CORS_ORIGINS: (env) => env.CORS_ORIGINS, }, }, // Maerchenzauber Mobile { path: 'apps/maerchenzauber/apps/mobile/.env', vars: { EXPO_PUBLIC_STORYTELLER_BACKEND_URL: (env) => `http://localhost:${env.MAERCHENZAUBER_BACKEND_PORT || '3003'}`, EXPO_ROUTER_APP_ROOT: () => 'app', }, }, // Maerchenzauber Web { path: 'apps/maerchenzauber/apps/web/.env', vars: { PUBLIC_SUPABASE_URL: (env) => env.MAERCHENZAUBER_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY: (env) => env.MAERCHENZAUBER_SUPABASE_ANON_KEY, PUBLIC_API_URL: (env) => `http://localhost:${env.MAERCHENZAUBER_BACKEND_PORT || '3003'}`, }, }, // Manacore Mobile { path: 'apps/manacore/apps/mobile/.env', vars: { EXPO_PUBLIC_SUPABASE_URL: (env) => env.MANACORE_SUPABASE_URL, EXPO_PUBLIC_SUPABASE_ANON_KEY: (env) => env.MANACORE_SUPABASE_ANON_KEY, }, }, // Manacore Web { path: 'apps/manacore/apps/web/.env', vars: { PUBLIC_SUPABASE_URL: (env) => env.MANACORE_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY: (env) => env.MANACORE_SUPABASE_ANON_KEY, MIDDLEWARE_URL: (env) => env.MANA_CORE_AUTH_URL, }, }, // Memoro Mobile { path: 'apps/memoro/apps/mobile/.env', vars: { EXPO_PUBLIC_SUPABASE_URL: (env) => env.MEMORO_SUPABASE_URL, EXPO_PUBLIC_SUPABASE_ANON_KEY: (env) => env.MEMORO_SUPABASE_ANON_KEY, EXPO_PUBLIC_MIDDLEWARE_API_URL: (env) => env.MEMORO_MIDDLEWARE_API_URL, EXPO_PUBLIC_APPID: (env) => env.MEMORO_APPID, }, }, // Memoro Web { path: 'apps/memoro/apps/web/.env', vars: { PUBLIC_SUPABASE_URL: (env) => env.MEMORO_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY: (env) => env.MEMORO_SUPABASE_ANON_KEY, }, }, // Manadeck Backend { path: 'apps/manadeck/apps/backend/.env', vars: { NODE_ENV: () => 'development', PORT: (env) => env.MANADECK_BACKEND_PORT || '3004', SUPABASE_URL: (env) => env.MANADECK_SUPABASE_URL, SUPABASE_ANON_KEY: (env) => env.MANADECK_SUPABASE_ANON_KEY, }, }, // Manadeck Web { path: 'apps/manadeck/apps/web/.env', vars: { PUBLIC_SUPABASE_URL: (env) => env.MANADECK_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY: (env) => env.MANADECK_SUPABASE_ANON_KEY, PUBLIC_API_URL: (env) => `http://localhost:${env.MANADECK_BACKEND_PORT || '3004'}`, PUBLIC_MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL, }, }, // Picture Backend (NestJS) { path: 'apps/picture/apps/backend/.env', vars: { NODE_ENV: () => 'development', PORT: () => '3003', DATABASE_URL: () => 'postgresql://picture:picturepassword@localhost:5434/picture', MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL, REPLICATE_API_TOKEN: (env) => env.MAERCHENZAUBER_REPLICATE_API_KEY, // Reuse existing Replicate key CORS_ORIGINS: (env) => env.CORS_ORIGINS, }, }, // Picture Mobile (Expo) { path: 'apps/picture/apps/mobile/.env', vars: { EXPO_PUBLIC_BACKEND_URL: (env) => env.PICTURE_BACKEND_URL || 'http://localhost:3003', EXPO_PUBLIC_MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL, }, }, // Picture Web (SvelteKit) { path: 'apps/picture/apps/web/.env', vars: { PUBLIC_BACKEND_URL: (env) => env.PICTURE_BACKEND_URL || 'http://localhost:3003', PUBLIC_MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL, PUBLIC_GOOGLE_CLIENT_ID: (env) => env.PICTURE_GOOGLE_CLIENT_ID || '', PUBLIC_APPLE_CLIENT_ID: (env) => env.PICTURE_APPLE_CLIENT_ID || '', }, }, // Zitare Backend (NestJS) { path: 'apps/zitare/apps/backend/.env', vars: { NODE_ENV: () => 'development', PORT: (env) => env.ZITARE_BACKEND_PORT || '3007', DATABASE_URL: (env) => env.ZITARE_DATABASE_URL, MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL, CORS_ORIGINS: (env) => env.CORS_ORIGINS, }, }, // Zitare Mobile (Expo) { path: 'apps/zitare/apps/mobile/.env', vars: { EXPO_PUBLIC_BACKEND_URL: (env) => `http://localhost:${env.ZITARE_BACKEND_PORT || '3007'}`, EXPO_PUBLIC_MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL, }, }, // Zitare Web (SvelteKit) { path: 'apps/zitare/apps/web/.env', vars: { PUBLIC_BACKEND_URL: (env) => `http://localhost:${env.ZITARE_BACKEND_PORT || '3007'}`, PUBLIC_MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL, }, }, // Presi Backend (NestJS) { path: 'apps/presi/apps/backend/.env', vars: { NODE_ENV: () => 'development', PORT: (env) => env.PRESI_BACKEND_PORT || '3008', DATABASE_URL: (env) => env.PRESI_DATABASE_URL, MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL, CORS_ORIGINS: (env) => env.CORS_ORIGINS, }, }, // Presi Mobile (Expo) { path: 'apps/presi/apps/mobile/.env', vars: { EXPO_PUBLIC_BACKEND_URL: (env) => `http://localhost:${env.PRESI_BACKEND_PORT || '3008'}`, EXPO_PUBLIC_MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL, }, }, // Presi Web (SvelteKit) { path: 'apps/presi/apps/web/.env', vars: { PUBLIC_BACKEND_URL: (env) => `http://localhost:${env.PRESI_BACKEND_PORT || '3008'}`, PUBLIC_MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL, }, }, // Mana Games Backend (NestJS) { path: 'games/mana-games/apps/backend/.env', vars: { NODE_ENV: () => 'development', PORT: (env) => env.MANA_GAMES_BACKEND_PORT || '3011', // Google Gemini GOOGLE_GENAI_API_KEY: (env) => env.MANA_GAMES_GOOGLE_GENAI_API_KEY, // Anthropic Claude ANTHROPIC_API_KEY: (env) => env.MANA_GAMES_ANTHROPIC_API_KEY, // Azure OpenAI AZURE_OPENAI_ENDPOINT: (env) => env.MANA_GAMES_AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY: (env) => env.MANA_GAMES_AZURE_OPENAI_API_KEY, AZURE_OPENAI_DEPLOYMENT: (env) => env.MANA_GAMES_AZURE_OPENAI_DEPLOYMENT || 'gpt-4o', // GitHub GITHUB_TOKEN: (env) => env.MANA_GAMES_GITHUB_TOKEN, GITHUB_OWNER: (env) => env.MANA_GAMES_GITHUB_OWNER || 'tillschneider', GITHUB_REPO: (env) => env.MANA_GAMES_GITHUB_REPO || 'mana-games', CORS_ORIGINS: (env) => env.CORS_ORIGINS, }, }, // Mana Games Web (Astro) { path: 'games/mana-games/apps/web/.env', vars: { PUBLIC_BACKEND_URL: (env) => `http://localhost:${env.MANA_GAMES_BACKEND_PORT || '3011'}`, }, }, ]; function main() { console.log('Generating environment files from .env.development...\n'); // Check if source file exists if (!existsSync(ENV_FILE)) { console.error(`Error: ${ENV_FILE} not found.`); console.error('Please create .env.development from .env.development.example'); process.exit(1); } // Parse source env file const sourceContent = readFileSync(ENV_FILE, 'utf-8'); const sourceEnv = parseEnvFile(sourceContent); let generated = 0; let skipped = 0; for (const config of APP_CONFIGS) { const targetPath = join(ROOT_DIR, config.path); const targetDir = dirname(targetPath); // Check if target directory exists if (!existsSync(targetDir)) { console.log(` Skipping ${config.path} (directory not found)`); skipped++; continue; } // Generate variables const targetVars = {}; for (const [key, getter] of Object.entries(config.vars)) { const value = getter(sourceEnv); if (value !== undefined && value !== null) { targetVars[key] = value; } } // Write file const content = generateEnvContent(targetVars); writeFileSync(targetPath, content); console.log(` Generated ${config.path}`); generated++; } console.log(`\nDone! Generated ${generated} files, skipped ${skipped}.`); console.log('\nNote: Generated .env files are gitignored. Only .env.development is committed.'); } main();