mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:21:10 +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
110
.env.development
Normal file
110
.env.development
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# ============================================
|
||||
# Mana Core Monorepo - Development Environment
|
||||
# ============================================
|
||||
# This is the central source of truth for all dev environment variables.
|
||||
# Run `pnpm setup:env` to generate app-specific .env files.
|
||||
#
|
||||
# DO NOT commit real API keys or production values here.
|
||||
# This file contains development/local values only.
|
||||
# ============================================
|
||||
|
||||
# ============================================
|
||||
# SHARED - Used across multiple apps
|
||||
# ============================================
|
||||
|
||||
# Mana Core Auth Service
|
||||
MANA_CORE_AUTH_URL=http://localhost:3001
|
||||
|
||||
# JWT Keys (shared across apps for token verification)
|
||||
JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDGRsOXROB4lprw\n9oXaOIt+cwHe3UxBOoiWiUXcpFuXwb+kBWn/LyjeCIOXtefOwE0S10JEodK+6foe\naqGHanq86qAmmkb4a8sjj5LAxXkHL35sJo8HaYcx5NkJQLxQSRHpTfdfxsKsKwxa\n4R4uqrvToqdo6tl/VMsGDPS8L7KzaiKaSdGugvlVtXWgV1soeXSUPyPwpyAXQg7h\nY4CkTSkJAplrs77RLdj8u6jbHKR3F7QkwiU1JocjhM1GP/suKiqXRu8omLFnu45C\ns09SNSRsOpNY5csrKA4PZ2LCks9VHH7HafFvB+BbRw4+Ssr6myOysAztqi3bZMRW\nLTakWpBbAgMBAAECggEAF5zi0IzaghHxhtkyYfrSRgSynX9+WYBRNu2ch8/SZqAj\neghOXMkZgAPEjtiSMDGqRsr4ReMoYtB2Qea8sOX8kwC1gj4Po1Mhtez0cwexclUf\nebLH3X/y9/1YiZJk5YImOMIuaoC/ELDvFOhIEhJcMbKREbIc+oiMcH6HgN0vViVh\nJptgHTnqnGHNARkEpf+xnxqJJxEgrEMz50b4fApKpoZsWXNnZ3Atc/i2ziGew5z4\npnGJxs9TWSukBZaQvl9iluBBvqmPkCOId+L7CmB44bNURpqQOm8gxEgLcdn06y5j\nIKee3Z4H6OTseFvSIYYqBqCyyyZWHICBZXUCDQKUbQKBgQDnFe+O+pQc5looLFiF\nxuYsfDtJqvoMgQ0BaVAo6wVpPe6w+1NA6ZxghcM0+8zyc70jZvdMXINhdsfWD5Gi\nJ/NEDI8EXJJKMfnFQ7F1Ad5NyTnnn/TsLda4GIGQznPRS6uxUP4ljFtxmU9G8Diz\nUQ47XsLjwzzbTedMTSYoQ46kdwKBgQDbp0dIq047o4A72/BBttKdZbgQmjFmqCXF\n8YRUquIDXh/CJ4OQwOIaOvk2398Rg53c3MsV+XCJaMmWYqnJ4BdITLsqeGKsczoS\nI0DMehDr++aOoX/f29r1c+7J/fV5jtAEUcwIEOR1vyAM+WdiWnnTvdpMPVUDsgaT\ntuH0E8WgPQKBgQCCINci87Z+Q7VXVAmRY7zwJhEY3eArNGzHc6+BKz+D0S1dmll6\nf1LhA9I2PuldSpGiovP1m08cjk/gGipPXyHdGxlaQmravyPA0urWUfQGZ59k8K1y\nZim4x4wGqEuN+4e2tT44lL5VzRhYgSPcznMuOaGTsrjNYiQy0mr/V3O25wKBgHvV\nryaVDaIp553XvXgO7ma2djNF+xv5KHKUWxqwzINBiX4YcOAnHlHTdbUuOcDSByoB\ngK1+16dgYGZccYTSxc2JFOw4usimndKj9WBSYT/p4G4BNuqqNKO1HKbceoxxq20E\nAJd7jpGjkxo9cb/Nammp22yoF0niEDsvG+xTSVOxAoGBAMfxHYCMdPc625upCbqG\nkPSJJGYREKGad80OtXilYXLvBPzV65q32k2YZGjaicPKRAzj72KO4nfIu9SY6bfO\nBvXCtIcvllZQuxyd3Cd8MirujJodKwThLTMd4bAYYMXGz1/W6R6pzunZs5KEpgEr\nczy9Gk9WNp0t8vfzyZZ9aago\n-----END PRIVATE KEY-----"
|
||||
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxkbDl0TgeJaa8PaF2jiL\nfnMB3t1MQTqIlolF3KRbl8G/pAVp/y8o3giDl7XnzsBNEtdCRKHSvun6Hmqhh2p6\nvOqgJppG+GvLI4+SwMV5By9+bCaPB2mHMeTZCUC8UEkR6U33X8bCrCsMWuEeLqq7\n06KnaOrZf1TLBgz0vC+ys2oimknRroL5VbV1oFdbKHl0lD8j8KcgF0IO4WOApE0p\nCQKZa7O+0S3Y/Luo2xykdxe0JMIlNSaHI4TNRj/7Lioql0bvKJixZ7uOQrNPUjUk\nbDqTWOXLKygOD2diwpLPVRx+x2nxbwfgW0c+Ssr6myOysAztqi3bZMRWLTakWpBb\nwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
|
||||
# Database (shared Postgres for local Docker)
|
||||
POSTGRES_USER=manacore
|
||||
POSTGRES_PASSWORD=devpassword
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=devpassword
|
||||
|
||||
# ============================================
|
||||
# MANA-CORE-AUTH SERVICE
|
||||
# ============================================
|
||||
|
||||
MANA_CORE_AUTH_PORT=3001
|
||||
MANA_CORE_AUTH_DATABASE_URL=postgresql://manacore:devpassword@localhost:5432/manacore
|
||||
JWT_ACCESS_TOKEN_EXPIRY=15m
|
||||
JWT_REFRESH_TOKEN_EXPIRY=7d
|
||||
JWT_ISSUER=manacore
|
||||
JWT_AUDIENCE=manacore
|
||||
CORS_ORIGINS=http://localhost:3000,http://localhost:3002,http://localhost:5173,http://localhost:8081
|
||||
CREDITS_SIGNUP_BONUS=150
|
||||
CREDITS_DAILY_FREE=5
|
||||
RATE_LIMIT_TTL=60
|
||||
RATE_LIMIT_MAX=100
|
||||
|
||||
# Stripe (test keys - get your own from Stripe dashboard)
|
||||
STRIPE_SECRET_KEY=sk_test_YOUR_KEY
|
||||
STRIPE_PUBLISHABLE_KEY=pk_test_YOUR_KEY
|
||||
STRIPE_WEBHOOK_SECRET=whsec_YOUR_SECRET
|
||||
|
||||
# ============================================
|
||||
# CHAT PROJECT
|
||||
# ============================================
|
||||
|
||||
# Chat Backend
|
||||
CHAT_BACKEND_PORT=3002
|
||||
CHAT_DATABASE_URL=postgresql://chat:chatpassword@localhost:5432/chat
|
||||
|
||||
# Azure OpenAI (required for chat - get your own keys)
|
||||
AZURE_OPENAI_ENDPOINT=https://your-azure-openai-endpoint.openai.azure.com
|
||||
AZURE_OPENAI_API_KEY=YOUR_API_KEY
|
||||
AZURE_OPENAI_API_VERSION=2024-12-01-preview
|
||||
|
||||
# Chat Supabase (if using Supabase for chat data)
|
||||
CHAT_SUPABASE_URL=https://your-chat-project.supabase.co
|
||||
CHAT_SUPABASE_ANON_KEY=your-supabase-anon-key
|
||||
|
||||
# ============================================
|
||||
# MAERCHENZAUBER PROJECT
|
||||
# ============================================
|
||||
|
||||
MAERCHENZAUBER_BACKEND_PORT=3003
|
||||
MAERCHENZAUBER_APP_ID=8d2f5ddb-e251-4b3b-8802-84022a7ac77f
|
||||
|
||||
# Supabase
|
||||
MAERCHENZAUBER_SUPABASE_URL=https://your-storyteller-project.supabase.co
|
||||
MAERCHENZAUBER_SUPABASE_ANON_KEY=your-supabase-anon-key
|
||||
MAERCHENZAUBER_JWT_SECRET=your-jwt-secret
|
||||
|
||||
# Azure OpenAI for story generation
|
||||
MAERCHENZAUBER_AZURE_OPENAI_KEY=YOUR_KEY
|
||||
MAERCHENZAUBER_AZURE_OPENAI_ENDPOINT=https://your-endpoint.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-08-01-preview
|
||||
|
||||
# Replicate for image generation
|
||||
MAERCHENZAUBER_REPLICATE_API_KEY=YOUR_KEY
|
||||
|
||||
# ============================================
|
||||
# MEMORO PROJECT
|
||||
# ============================================
|
||||
|
||||
MEMORO_SUPABASE_URL=https://your-memoro-project.supabase.co
|
||||
MEMORO_SUPABASE_ANON_KEY=your-supabase-anon-key
|
||||
MEMORO_MIDDLEWARE_API_URL=https://mana-core-middleware-111768794939.europe-west3.run.app
|
||||
MEMORO_APPID=your-app-id
|
||||
|
||||
# ============================================
|
||||
# MANACORE PROJECT
|
||||
# ============================================
|
||||
|
||||
MANACORE_SUPABASE_URL=https://your-manacore-project.supabase.co
|
||||
MANACORE_SUPABASE_ANON_KEY=your-supabase-anon-key
|
||||
|
||||
# ============================================
|
||||
# MANADECK PROJECT
|
||||
# ============================================
|
||||
|
||||
MANADECK_BACKEND_PORT=3004
|
||||
MANADECK_SUPABASE_URL=https://your-manadeck-project.supabase.co
|
||||
MANADECK_SUPABASE_ANON_KEY=your-supabase-anon-key
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -25,6 +25,9 @@ ios/
|
|||
.env.production.local
|
||||
.env*.local
|
||||
|
||||
# BUT commit the central development env file
|
||||
!.env.development
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
|
|
|
|||
31
CLAUDE.md
31
CLAUDE.md
|
|
@ -169,7 +169,34 @@ pnpm add <package> --filter @manacore/shared-utils
|
|||
|
||||
## Environment Variables
|
||||
|
||||
Each project/app has its own `.env` file. Common patterns:
|
||||
### Centralized Development Environment
|
||||
|
||||
All development environment variables are managed from a single file: `.env.development`
|
||||
|
||||
```bash
|
||||
# First-time setup: generates all app-specific .env files
|
||||
pnpm setup:env
|
||||
|
||||
# This also runs automatically after `pnpm install`
|
||||
```
|
||||
|
||||
The script reads `.env.development` and generates platform-specific `.env` files for each app with the correct prefixes:
|
||||
- **Expo mobile**: `EXPO_PUBLIC_*` prefix
|
||||
- **SvelteKit web**: `PUBLIC_*` prefix
|
||||
- **NestJS backend**: No prefix
|
||||
|
||||
### Key Files
|
||||
- `.env.development` - Central source of truth (committed to git)
|
||||
- `scripts/generate-env.mjs` - Generation script
|
||||
- `apps/**/apps/**/.env` - Generated files (gitignored)
|
||||
|
||||
### Adding New Variables
|
||||
|
||||
1. Add the variable to `.env.development`
|
||||
2. Update `scripts/generate-env.mjs` to map it to the appropriate apps
|
||||
3. Run `pnpm setup:env` to regenerate
|
||||
|
||||
### Platform Prefix Patterns
|
||||
|
||||
**Mobile (Expo):**
|
||||
```
|
||||
|
|
@ -193,6 +220,8 @@ PORT=...
|
|||
|
||||
## Project-Specific Documentation
|
||||
|
||||
- **[docs/ENVIRONMENT_VARIABLES.md](docs/ENVIRONMENT_VARIABLES.md)** - Complete environment setup guide
|
||||
|
||||
Each project has its own `CLAUDE.md` with detailed information:
|
||||
- `apps/maerchenzauber/CLAUDE.md` - Story generation specifics, AI services
|
||||
- `apps/manacore/CLAUDE.md` - Multi-app ecosystem, auth details
|
||||
|
|
|
|||
256
docs/ENVIRONMENT_VARIABLES.md
Normal file
256
docs/ENVIRONMENT_VARIABLES.md
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
# Environment Variables Guide
|
||||
|
||||
This document explains the centralized environment variable system for the Mana Core monorepo.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# After cloning the repo, install dependencies (auto-generates .env files)
|
||||
pnpm install
|
||||
|
||||
# Or manually generate .env files
|
||||
pnpm setup:env
|
||||
```
|
||||
|
||||
That's it! All app-specific `.env` files are generated from `.env.development`.
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
.env.development # Central config (committed)
|
||||
│
|
||||
▼
|
||||
scripts/generate-env.mjs # Transforms variables
|
||||
│
|
||||
▼
|
||||
apps/**/apps/**/.env # Generated files (gitignored)
|
||||
```
|
||||
|
||||
The generator reads `.env.development` and creates app-specific `.env` files with the correct prefixes for each platform:
|
||||
|
||||
| Platform | Prefix | Example |
|
||||
|----------|--------|---------|
|
||||
| Expo (mobile) | `EXPO_PUBLIC_` | `EXPO_PUBLIC_SUPABASE_URL` |
|
||||
| SvelteKit (web) | `PUBLIC_` | `PUBLIC_SUPABASE_URL` |
|
||||
| NestJS (backend) | None | `SUPABASE_URL` |
|
||||
|
||||
## File Locations
|
||||
|
||||
### Source File
|
||||
- **`.env.development`** - Single source of truth, committed to git
|
||||
|
||||
### Generated Files (gitignored)
|
||||
- `services/mana-core-auth/.env`
|
||||
- `apps/chat/apps/backend/.env`
|
||||
- `apps/chat/apps/mobile/.env`
|
||||
- `apps/chat/apps/web/.env`
|
||||
- `apps/maerchenzauber/apps/backend/.env`
|
||||
- `apps/maerchenzauber/apps/mobile/.env`
|
||||
- `apps/maerchenzauber/apps/web/.env`
|
||||
- `apps/manacore/apps/mobile/.env`
|
||||
- `apps/manacore/apps/web/.env`
|
||||
- `apps/memoro/apps/mobile/.env`
|
||||
- `apps/memoro/apps/web/.env`
|
||||
- `apps/manadeck/apps/backend/.env`
|
||||
- `apps/manadeck/apps/web/.env`
|
||||
|
||||
## Variable Reference
|
||||
|
||||
### Shared Variables
|
||||
|
||||
| Variable | Description | Used By |
|
||||
|----------|-------------|---------|
|
||||
| `MANA_CORE_AUTH_URL` | Auth service URL | All apps |
|
||||
| `JWT_PRIVATE_KEY` | JWT signing key | mana-core-auth |
|
||||
| `JWT_PUBLIC_KEY` | JWT verification key | All backends |
|
||||
| `POSTGRES_USER` | Database user | Docker, backends |
|
||||
| `POSTGRES_PASSWORD` | Database password | Docker, backends |
|
||||
| `REDIS_HOST` | Redis host | mana-core-auth |
|
||||
| `REDIS_PORT` | Redis port | mana-core-auth |
|
||||
| `REDIS_PASSWORD` | Redis password | mana-core-auth |
|
||||
|
||||
### Mana Core Auth Service
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `MANA_CORE_AUTH_PORT` | Service port | `3001` |
|
||||
| `MANA_CORE_AUTH_DATABASE_URL` | PostgreSQL connection string | - |
|
||||
| `JWT_ACCESS_TOKEN_EXPIRY` | Access token TTL | `15m` |
|
||||
| `JWT_REFRESH_TOKEN_EXPIRY` | Refresh token TTL | `7d` |
|
||||
| `JWT_ISSUER` | JWT issuer claim | `manacore` |
|
||||
| `JWT_AUDIENCE` | JWT audience claim | `manacore` |
|
||||
| `STRIPE_SECRET_KEY` | Stripe secret key | - |
|
||||
| `STRIPE_PUBLISHABLE_KEY` | Stripe publishable key | - |
|
||||
| `STRIPE_WEBHOOK_SECRET` | Stripe webhook secret | - |
|
||||
| `CORS_ORIGINS` | Allowed CORS origins | - |
|
||||
| `CREDITS_SIGNUP_BONUS` | Credits on signup | `150` |
|
||||
| `CREDITS_DAILY_FREE` | Daily free credits | `5` |
|
||||
| `RATE_LIMIT_TTL` | Rate limit window (seconds) | `60` |
|
||||
| `RATE_LIMIT_MAX` | Max requests per window | `100` |
|
||||
|
||||
### Chat Project
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `CHAT_BACKEND_PORT` | Backend service port | `3002` |
|
||||
| `CHAT_DATABASE_URL` | PostgreSQL connection string | - |
|
||||
| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI endpoint URL | - |
|
||||
| `AZURE_OPENAI_API_KEY` | Azure OpenAI API key | - |
|
||||
| `AZURE_OPENAI_API_VERSION` | API version | `2024-12-01-preview` |
|
||||
| `CHAT_SUPABASE_URL` | Supabase project URL | - |
|
||||
| `CHAT_SUPABASE_ANON_KEY` | Supabase anonymous key | - |
|
||||
|
||||
### Maerchenzauber Project
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `MAERCHENZAUBER_BACKEND_PORT` | Backend service port | `3003` |
|
||||
| `MAERCHENZAUBER_APP_ID` | Mana Core app ID | - |
|
||||
| `MAERCHENZAUBER_SUPABASE_URL` | Supabase project URL | - |
|
||||
| `MAERCHENZAUBER_SUPABASE_ANON_KEY` | Supabase anonymous key | - |
|
||||
| `MAERCHENZAUBER_JWT_SECRET` | JWT secret for Supabase | - |
|
||||
| `MAERCHENZAUBER_AZURE_OPENAI_KEY` | Azure OpenAI key | - |
|
||||
| `MAERCHENZAUBER_AZURE_OPENAI_ENDPOINT` | Azure OpenAI endpoint | - |
|
||||
| `MAERCHENZAUBER_REPLICATE_API_KEY` | Replicate API key (images) | - |
|
||||
|
||||
### Memoro Project
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `MEMORO_SUPABASE_URL` | Supabase project URL |
|
||||
| `MEMORO_SUPABASE_ANON_KEY` | Supabase anonymous key |
|
||||
| `MEMORO_MIDDLEWARE_API_URL` | Middleware API URL |
|
||||
| `MEMORO_APPID` | Mana Core app ID |
|
||||
|
||||
### Manacore Project
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `MANACORE_SUPABASE_URL` | Supabase project URL |
|
||||
| `MANACORE_SUPABASE_ANON_KEY` | Supabase anonymous key |
|
||||
|
||||
### Manadeck Project
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `MANADECK_BACKEND_PORT` | Backend service port | `3004` |
|
||||
| `MANADECK_SUPABASE_URL` | Supabase project URL | - |
|
||||
| `MANADECK_SUPABASE_ANON_KEY` | Supabase anonymous key | - |
|
||||
|
||||
## Adding New Variables
|
||||
|
||||
### Step 1: Add to `.env.development`
|
||||
|
||||
```bash
|
||||
# In .env.development
|
||||
MY_NEW_PROJECT_API_KEY=your-api-key
|
||||
MY_NEW_PROJECT_URL=https://api.example.com
|
||||
```
|
||||
|
||||
### Step 2: Update the Generator Script
|
||||
|
||||
Edit `scripts/generate-env.mjs` and add your app config:
|
||||
|
||||
```javascript
|
||||
// In APP_CONFIGS array
|
||||
{
|
||||
path: 'apps/my-project/apps/mobile/.env',
|
||||
vars: {
|
||||
// For Expo, add EXPO_PUBLIC_ prefix
|
||||
EXPO_PUBLIC_API_KEY: (env) => env.MY_NEW_PROJECT_API_KEY,
|
||||
EXPO_PUBLIC_API_URL: (env) => env.MY_NEW_PROJECT_URL,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'apps/my-project/apps/web/.env',
|
||||
vars: {
|
||||
// For SvelteKit, add PUBLIC_ prefix
|
||||
PUBLIC_API_KEY: (env) => env.MY_NEW_PROJECT_API_KEY,
|
||||
PUBLIC_API_URL: (env) => env.MY_NEW_PROJECT_URL,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'apps/my-project/apps/backend/.env',
|
||||
vars: {
|
||||
// For NestJS, no prefix needed
|
||||
API_KEY: (env) => env.MY_NEW_PROJECT_API_KEY,
|
||||
API_URL: (env) => env.MY_NEW_PROJECT_URL,
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
### Step 3: Regenerate
|
||||
|
||||
```bash
|
||||
pnpm setup:env
|
||||
```
|
||||
|
||||
## Local Overrides
|
||||
|
||||
If you need to override variables locally without affecting others:
|
||||
|
||||
1. The generated `.env` files are gitignored
|
||||
2. You can manually edit them after generation
|
||||
3. Or create `.env.local` files (also gitignored) that some frameworks auto-load
|
||||
|
||||
**Note:** Running `pnpm setup:env` will overwrite your changes, so use `.env.local` for persistent overrides.
|
||||
|
||||
## Docker Integration
|
||||
|
||||
The root `.env.development` is also used by Docker Compose:
|
||||
|
||||
```bash
|
||||
# Start all services with shared env
|
||||
pnpm docker:up:all
|
||||
```
|
||||
|
||||
Docker services read from:
|
||||
- Root `.env.development` for shared values
|
||||
- Service-specific `.env` files for service-specific values
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Variable is undefined" Error
|
||||
|
||||
1. Check if the variable exists in `.env.development`
|
||||
2. Run `pnpm setup:env` to regenerate
|
||||
3. Restart your dev server (env changes require restart)
|
||||
|
||||
### Generated File Has Wrong Value
|
||||
|
||||
1. Check the mapping in `scripts/generate-env.mjs`
|
||||
2. Ensure the source variable name matches exactly
|
||||
3. Run `pnpm setup:env` again
|
||||
|
||||
### New App Not Getting Generated
|
||||
|
||||
1. Add app config to `APP_CONFIGS` in `scripts/generate-env.mjs`
|
||||
2. Ensure the target directory exists
|
||||
3. Run `pnpm setup:env`
|
||||
|
||||
### Expo Not Picking Up Changes
|
||||
|
||||
Expo caches environment variables. Clear the cache:
|
||||
|
||||
```bash
|
||||
cd apps/<project>/apps/mobile
|
||||
npx expo start -c
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
- `.env.development` contains **development-only** values
|
||||
- Never put production secrets in this file
|
||||
- The JWT keys in `.env.development` are for local development only
|
||||
- Use separate secrets management for production (1Password, AWS Secrets Manager, etc.)
|
||||
|
||||
## Migration from Old System
|
||||
|
||||
If you have existing `.env` files with real values:
|
||||
|
||||
1. Copy important values to `.env.development`
|
||||
2. Delete the old `.env` files
|
||||
3. Run `pnpm setup:env`
|
||||
4. Verify apps still work
|
||||
|
||||
The old `.env.example` files can remain as documentation.
|
||||
|
|
@ -13,6 +13,9 @@
|
|||
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md,svelte,astro}\"",
|
||||
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md,svelte,astro}\"",
|
||||
|
||||
"setup:env": "node scripts/generate-env.mjs",
|
||||
"postinstall": "node scripts/generate-env.mjs || true",
|
||||
|
||||
"maerchenzauber:dev": "turbo run dev --filter=maerchenzauber...",
|
||||
"manacore:dev": "turbo run dev --filter=manacore...",
|
||||
"manadeck:dev": "turbo run dev --filter=manadeck...",
|
||||
|
|
|
|||
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();
|
||||
10
turbo.json
10
turbo.json
|
|
@ -1,9 +1,17 @@
|
|||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"globalEnv": [
|
||||
"NODE_ENV",
|
||||
"MANA_CORE_AUTH_URL",
|
||||
"JWT_PUBLIC_KEY",
|
||||
"AZURE_OPENAI_ENDPOINT",
|
||||
"AZURE_OPENAI_API_KEY"
|
||||
],
|
||||
"tasks": {
|
||||
"dev": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
"persistent": true,
|
||||
"dotEnv": [".env", ".env.development", ".env.local"]
|
||||
},
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue