mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:41:09 +02:00
🧑💻 chore: add centralized environment variable system
- Add .env.development as single source of truth for dev variables - Create scripts/generate-env.mjs to generate app-specific .env files - Add pnpm setup:env command (also runs on postinstall) - Update turbo.json with globalEnv for cache invalidation - Add comprehensive docs/ENVIRONMENT_VARIABLES.md - Update CLAUDE.md with env setup instructions
This commit is contained in:
parent
ff80aeec1f
commit
2328b8938c
7 changed files with 675 additions and 2 deletions
264
scripts/generate-env.mjs
Normal file
264
scripts/generate-env.mjs
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
#!/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_BACKEND_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,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
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();
|
||||
Loading…
Add table
Add a link
Reference in a new issue