mirror of https://github.com/buster-so/buster.git
Add centralized environment variable management with env-utils package
Co-authored-by: natemkelley <natemkelley@gmail.com>
This commit is contained in:
parent
ae96e2f8b1
commit
c1c7b37942
94
.env.example
94
.env.example
|
@ -1,39 +1,63 @@
|
||||||
# API VARS
|
# Environment Configuration
|
||||||
ENVIRONMENT="local"
|
# Copy this file to .env and fill in the values
|
||||||
BUSTER_URL="http://localhost:3000"
|
|
||||||
JWT_SECRET="super-secret-jwt-token-with-at-least-32-characters-long"
|
|
||||||
BUSTER_WH_TOKEN="buster-wh-token"
|
|
||||||
DATABASE_URL="postgresql://postgres.your-tenant-id:your-super-secret-and-long-postgres-password@supavisor:5432/postgres"
|
|
||||||
POOLER_URL="postgresql://postgres.your-tenant-id:your-super-secret-and-long-postgres-password@supavisor:5432/postgres"
|
|
||||||
REDIS_URL="redis://buster-redis:6379"
|
|
||||||
LOG_LEVEL="debug"
|
|
||||||
SUPABASE_URL="http://kong:8000"
|
|
||||||
SUPABASE_SERVICE_ROLE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey AgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
|
|
||||||
POSTHOG_TELEMETRY_KEY="phc_zZraCicSTfeXX5b9wWQv2rWG8QB4Z3xlotOT7gFtoNi"
|
|
||||||
TELEMETRY_ENABLED="true"
|
|
||||||
MAX_RECURSION="15"
|
|
||||||
SUPABASE_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey AgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE"
|
|
||||||
|
|
||||||
# AI VARS
|
# General Environment
|
||||||
RERANK_API_KEY="your_rerank_api_key"
|
NODE_ENV=development
|
||||||
RERANK_MODEL="rerank-v3.5"
|
ENVIRONMENT=development
|
||||||
RERANK_BASE_URL="https://api.cohere.com/v2/rerank"
|
TELEMETRY_ENABLED=false
|
||||||
LLM_API_KEY="your_llm_api_key"
|
LOG_LEVEL=info
|
||||||
LLM_BASE_URL="http://buster-litellm:4001"
|
|
||||||
|
|
||||||
# WEB VARS
|
# Database
|
||||||
NEXT_PUBLIC_API_URL="http://localhost:3001" # External URL for the API service (buster-api)
|
DATABASE_URL=
|
||||||
NEXT_PUBLIC_URL="http://localhost:3000" # External URL for the Web service (buster-web)
|
|
||||||
NEXT_PUBLIC_SUPABASE_URL="http://kong:8000" # External URL for Supabase (Kong proxy)
|
|
||||||
NEXT_PUBLIC_WS_URL="ws://localhost:3001"
|
|
||||||
NEXT_PUBLIC_SUPABASE_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey AgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE"
|
|
||||||
NEXT_PRIVATE_SUPABASE_SERVICE_ROLE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey AgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q"
|
|
||||||
|
|
||||||
# TS SERVER
|
# Supabase
|
||||||
SERVER_PORT=3002
|
SUPABASE_URL=
|
||||||
|
SUPABASE_SERVICE_ROLE_KEY=
|
||||||
|
SUPABASE_ANON_KEY=
|
||||||
|
SUPABASE_PUBLIC_URL=
|
||||||
|
|
||||||
# ELECTRIC
|
# Next.js Public Variables (Frontend)
|
||||||
ELECTRIC_PROXY_URL=http://localhost:3003
|
NEXT_PUBLIC_API_URL=
|
||||||
ELECTRIC_PORT=3003
|
NEXT_PUBLIC_API2_URL=
|
||||||
ELECTRIC_INSECURE=false
|
NEXT_PUBLIC_WEB_SOCKET_URL=
|
||||||
ELECTRIC_SECRET=my-little-buttercup-has-the-sweetest-smile
|
NEXT_PUBLIC_URL=
|
||||||
|
NEXT_PUBLIC_SUPABASE_URL=
|
||||||
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
||||||
|
NEXT_PUBLIC_WS_URL=
|
||||||
|
NEXT_PUBLIC_POSTHOG_KEY=
|
||||||
|
NEXT_PUBLIC_POSTHOG_HOST=
|
||||||
|
|
||||||
|
# Next.js Private Variables
|
||||||
|
NEXT_SLACK_APP_SUPPORT_URL=
|
||||||
|
NEXT_PRIVATE_SUPABASE_SERVICE_ROLE_KEY=
|
||||||
|
|
||||||
|
# Server Configuration
|
||||||
|
SERVER_PORT=3000
|
||||||
|
|
||||||
|
# Electric SQL
|
||||||
|
ELECTRIC_PROXY_URL=
|
||||||
|
ELECTRIC_PORT=
|
||||||
|
ELECTRIC_INSECURE=
|
||||||
|
ELECTRIC_SECRET=
|
||||||
|
ELECTRIC_SOURCE_ID=
|
||||||
|
|
||||||
|
# Rerank API
|
||||||
|
RERANK_API_KEY=
|
||||||
|
RERANK_MODEL=
|
||||||
|
RERANK_BASE_URL=
|
||||||
|
|
||||||
|
# LLM APIs
|
||||||
|
LLM_API_KEY=
|
||||||
|
LLM_BASE_URL=
|
||||||
|
OPENAI_API_KEY=
|
||||||
|
ANTHROPIC_API_KEY=
|
||||||
|
|
||||||
|
# Analytics
|
||||||
|
POSTHOG_TELEMETRY_KEY=
|
||||||
|
|
||||||
|
# Services
|
||||||
|
BRAINTRUST_KEY=
|
||||||
|
TRIGGER_SECRET_KEY=
|
||||||
|
|
||||||
|
# Playwright Testing
|
||||||
|
PLAYWRIGHT_START_COMMAND=
|
||||||
|
|
52
CLAUDE.md
52
CLAUDE.md
|
@ -49,6 +49,58 @@ When writing code, follow this workflow to ensure code quality:
|
||||||
- Keep business logic separate from infrastructure concerns
|
- Keep business logic separate from infrastructure concerns
|
||||||
- Use proper error handling at each level
|
- Use proper error handling at each level
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
This project uses a centralized environment variable system:
|
||||||
|
|
||||||
|
1. **All environment variables are defined at the root level** in a single `.env` file
|
||||||
|
2. **Turbo passes these variables** to all packages via the `globalEnv` configuration in `turbo.json`
|
||||||
|
3. **Individual packages validate** their required environment variables using the shared `@buster/env-utils` package
|
||||||
|
|
||||||
|
### Setting Up Environment Variables
|
||||||
|
|
||||||
|
1. Copy `.env.example` to `.env` at the project root:
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Fill in the required values in `.env`
|
||||||
|
|
||||||
|
3. All packages will automatically have access to these variables through Turbo
|
||||||
|
|
||||||
|
### Adding New Environment Variables
|
||||||
|
|
||||||
|
When adding new environment variables:
|
||||||
|
|
||||||
|
1. Add the variable to `.env.example` with a descriptive comment
|
||||||
|
2. Add the variable name to the `globalEnv` array in `turbo.json`
|
||||||
|
3. Update the package's `validate-env.js` script to include the new variable
|
||||||
|
4. Update the package's `env.d.ts` file with the TypeScript type definition
|
||||||
|
|
||||||
|
### Migrating Packages to Centralized Env
|
||||||
|
|
||||||
|
To migrate an existing package to use the centralized environment system:
|
||||||
|
|
||||||
|
1. Remove any local `.env` files from the package
|
||||||
|
2. Add `@buster/env-utils` as a dependency:
|
||||||
|
```json
|
||||||
|
"@buster/env-utils": "workspace:*"
|
||||||
|
```
|
||||||
|
3. Update the package's `scripts/validate-env.js` to use the shared utilities:
|
||||||
|
```javascript
|
||||||
|
import { loadRootEnv, validateEnv } from '@buster/env-utils';
|
||||||
|
|
||||||
|
loadRootEnv();
|
||||||
|
|
||||||
|
const requiredEnv = {
|
||||||
|
DATABASE_URL: process.env.DATABASE_URL,
|
||||||
|
// ... other required variables
|
||||||
|
};
|
||||||
|
|
||||||
|
const { hasErrors } = validateEnv(requiredEnv);
|
||||||
|
if (hasErrors) process.exit(1);
|
||||||
|
```
|
||||||
|
|
||||||
### 3. Ensure Type Safety
|
### 3. Ensure Type Safety
|
||||||
```bash
|
```bash
|
||||||
# Build entire monorepo to check types
|
# Build entire monorepo to check types
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
"@buster/access-controls": "workspace:*",
|
"@buster/access-controls": "workspace:*",
|
||||||
"@buster/data-source": "workspace:*",
|
"@buster/data-source": "workspace:*",
|
||||||
"@buster/database": "workspace:*",
|
"@buster/database": "workspace:*",
|
||||||
|
"@buster/env-utils": "workspace:*",
|
||||||
"@buster/sandbox": "workspace:*",
|
"@buster/sandbox": "workspace:*",
|
||||||
"@buster/server-shared": "workspace:*",
|
"@buster/server-shared": "workspace:*",
|
||||||
"@buster/stored-values": "workspace:*",
|
"@buster/stored-values": "workspace:*",
|
||||||
|
|
|
@ -1,22 +1,13 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
// Load environment variables from .env file
|
// This script uses the shared env-utils to validate environment variables
|
||||||
import { config } from 'dotenv';
|
import { loadRootEnv, validateEnv } from '@buster/env-utils';
|
||||||
config();
|
|
||||||
|
|
||||||
// Build-time environment validation
|
// Load environment variables from root .env file
|
||||||
|
loadRootEnv();
|
||||||
|
|
||||||
console.info('🔍 Validating environment variables...');
|
// Define required environment variables for this package
|
||||||
|
const requiredEnv = {
|
||||||
// Skip validation during Docker builds (environment variables are only available at runtime)
|
|
||||||
if (process.env.DOCKER_BUILD || process.env.CI || process.env.NODE_ENV === 'production') {
|
|
||||||
console.info(
|
|
||||||
'🐳 Docker/CI build detected - skipping environment validation (will validate at runtime)'
|
|
||||||
);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const env = {
|
|
||||||
BRAINTRUST_KEY: process.env.BRAINTRUST_KEY,
|
BRAINTRUST_KEY: process.env.BRAINTRUST_KEY,
|
||||||
PATH: process.env.PATH,
|
PATH: process.env.PATH,
|
||||||
HOME: process.env.HOME,
|
HOME: process.env.HOME,
|
||||||
|
@ -24,25 +15,11 @@ const env = {
|
||||||
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
|
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
|
||||||
ENVIRONMENT: process.env.ENVIRONMENT,
|
ENVIRONMENT: process.env.ENVIRONMENT,
|
||||||
DATABASE_URL: process.env.DATABASE_URL,
|
DATABASE_URL: process.env.DATABASE_URL,
|
||||||
NODE_ENV: process.env.NODE_ENV || 'development',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let hasErrors = false;
|
// Validate environment variables
|
||||||
|
const { hasErrors } = validateEnv(requiredEnv);
|
||||||
for (const [envKey, value] of Object.entries(env)) {
|
|
||||||
if (!value) {
|
|
||||||
console.error(`❌ Missing required environment variable: ${envKey}`);
|
|
||||||
hasErrors = true;
|
|
||||||
} else {
|
|
||||||
console.info(`✅ ${envKey} is set`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasErrors) {
|
if (hasErrors) {
|
||||||
console.error('');
|
|
||||||
console.error('❌ Build cannot continue with missing environment variables.');
|
|
||||||
console.error('Please check your .env file and ensure all required variables are set.');
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.info('✅ All required environment variables are present');
|
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@buster/env-utils": "workspace:*",
|
||||||
"@buster/typescript-config": "workspace:*",
|
"@buster/typescript-config": "workspace:*",
|
||||||
"@buster/vitest-config": "workspace:*",
|
"@buster/vitest-config": "workspace:*",
|
||||||
"ai": "catalog:",
|
"ai": "catalog:",
|
||||||
|
|
|
@ -1,44 +1,22 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
// Load environment variables from .env file
|
// This script uses the shared env-utils to validate environment variables
|
||||||
import { config } from 'dotenv';
|
import { loadRootEnv, validateEnv } from '@buster/env-utils';
|
||||||
config();
|
|
||||||
|
|
||||||
// Build-time environment validation
|
// Load environment variables from root .env file
|
||||||
console.log('🔍 Validating environment variables...');
|
loadRootEnv();
|
||||||
|
|
||||||
// Skip validation during Docker builds (environment variables are only available at runtime)
|
// Define required environment variables for this package
|
||||||
if (process.env.DOCKER_BUILD || process.env.CI || process.env.NODE_ENV === 'production') {
|
const requiredEnv = {
|
||||||
console.log(
|
|
||||||
'🐳 Docker/CI build detected - skipping environment validation (will validate at runtime)'
|
|
||||||
);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const env = {
|
|
||||||
DATABASE_URL: process.env.DATABASE_URL,
|
DATABASE_URL: process.env.DATABASE_URL,
|
||||||
SUPABASE_URL: process.env.SUPABASE_URL,
|
SUPABASE_URL: process.env.SUPABASE_URL,
|
||||||
SUPABASE_SERVICE_ROLE_KEY: process.env.SUPABASE_SERVICE_ROLE_KEY,
|
SUPABASE_SERVICE_ROLE_KEY: process.env.SUPABASE_SERVICE_ROLE_KEY,
|
||||||
SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY,
|
SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY,
|
||||||
NODE_ENV: process.env.NODE_ENV || 'development',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let hasErrors = false;
|
// Validate environment variables
|
||||||
|
const { hasErrors } = validateEnv(requiredEnv);
|
||||||
for (const [envKey, value] of Object.entries(env)) {
|
|
||||||
if (!value) {
|
|
||||||
console.error(`❌ Missing required environment variable: ${envKey}`);
|
|
||||||
hasErrors = true;
|
|
||||||
} else {
|
|
||||||
console.log(`✅ ${envKey} is set`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasErrors) {
|
if (hasErrors) {
|
||||||
console.error('');
|
|
||||||
console.error('❌ Build cannot continue with missing environment variables.');
|
|
||||||
console.error('Please check your .env file and ensure all required variables are set.');
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ All required environment variables are present');
|
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
# @buster/env-utils
|
||||||
|
|
||||||
|
Shared utilities for environment variable validation across the monorepo.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This package provides utilities to:
|
||||||
|
1. Load environment variables from the root `.env` file
|
||||||
|
2. Validate required environment variables
|
||||||
|
3. Skip validation in CI/Docker/Production environments
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### In your package's validate-env.js script:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { loadRootEnv, validateEnv } from '@buster/env-utils';
|
||||||
|
|
||||||
|
// Load environment variables from root .env file
|
||||||
|
loadRootEnv();
|
||||||
|
|
||||||
|
// Define required environment variables for this package
|
||||||
|
const requiredEnv = {
|
||||||
|
DATABASE_URL: process.env.DATABASE_URL,
|
||||||
|
API_KEY: process.env.API_KEY,
|
||||||
|
// Add your required variables here
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate environment variables
|
||||||
|
const { hasErrors } = validateEnv(requiredEnv);
|
||||||
|
|
||||||
|
if (hasErrors) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding env-utils to your package:
|
||||||
|
|
||||||
|
1. Add the dependency to your package.json:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@buster/env-utils": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Update your validate-env.js script as shown above
|
||||||
|
|
||||||
|
3. Make sure your required environment variables are defined in the root `.env` file
|
||||||
|
|
||||||
|
## Migration Guide
|
||||||
|
|
||||||
|
To migrate an existing package to use the centralized env system:
|
||||||
|
|
||||||
|
1. Remove any local `.env` files from your package
|
||||||
|
2. Add `@buster/env-utils` as a dependency
|
||||||
|
3. Update your `scripts/validate-env.js` to use the shared utilities
|
||||||
|
4. Move any package-specific env variables to the root `.env` file
|
||||||
|
5. Ensure all env variables are listed in the root `turbo.json` globalEnv array
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### `loadRootEnv()`
|
||||||
|
Loads environment variables from the root `.env` file.
|
||||||
|
|
||||||
|
### `validateEnv(requiredVars, options?)`
|
||||||
|
Validates that all required environment variables are set.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- `requiredVars`: Object mapping env var names to their values
|
||||||
|
- `options`: Optional configuration
|
||||||
|
- `skipInCI`: Skip validation in CI (default: true)
|
||||||
|
- `skipInProduction`: Skip validation in production (default: true)
|
||||||
|
- `skipInDocker`: Skip validation in Docker builds (default: true)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- `{ hasErrors: boolean, missingVariables: string[] }`
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||||
|
"extends": ["../../biome.json"],
|
||||||
|
"files": {
|
||||||
|
"include": ["src/**/*"]
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,13 @@
|
||||||
|
export declare function loadRootEnv(): void;
|
||||||
|
export interface EnvValidationResult {
|
||||||
|
hasErrors: boolean;
|
||||||
|
missingVariables: string[];
|
||||||
|
}
|
||||||
|
export interface EnvValidationOptions {
|
||||||
|
skipInCI?: boolean;
|
||||||
|
skipInProduction?: boolean;
|
||||||
|
skipInDocker?: boolean;
|
||||||
|
}
|
||||||
|
export declare function validateEnv(requiredVars: Record<string, string | undefined>, options?: EnvValidationOptions): EnvValidationResult;
|
||||||
|
export declare function createValidateEnvScript(requiredEnvVars: string[]): string;
|
||||||
|
//# sourceMappingURL=index.d.ts.map
|
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAsBA,wBAAgB,WAAW,IAAI,IAAI,CAKlC;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAGD,wBAAgB,WAAW,CACzB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EAChD,OAAO,GAAE,oBAAyB,GACjC,mBAAmB,CA4CrB;AAGD,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,MAAM,CAyBzE"}
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { config } from 'dotenv';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
// Get the directory of the current module
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
// Find the root of the monorepo (where turbo.json is)
|
||||||
|
function findMonorepoRoot() {
|
||||||
|
let currentDir = __dirname;
|
||||||
|
while (currentDir !== '/') {
|
||||||
|
if (existsSync(path.join(currentDir, 'turbo.json'))) {
|
||||||
|
return currentDir;
|
||||||
|
}
|
||||||
|
currentDir = path.dirname(currentDir);
|
||||||
|
}
|
||||||
|
throw new Error('Could not find monorepo root (turbo.json)');
|
||||||
|
}
|
||||||
|
// Load environment variables from root .env file
|
||||||
|
export function loadRootEnv() {
|
||||||
|
const rootDir = findMonorepoRoot();
|
||||||
|
const envPath = path.join(rootDir, '.env');
|
||||||
|
config({ path: envPath });
|
||||||
|
}
|
||||||
|
// Validate required environment variables
|
||||||
|
export function validateEnv(requiredVars, options = {}) {
|
||||||
|
const { skipInCI = true, skipInProduction = true, skipInDocker = true, } = options;
|
||||||
|
console.info('🔍 Validating environment variables...');
|
||||||
|
// Skip validation in certain environments
|
||||||
|
if ((skipInDocker && process.env.DOCKER_BUILD) ||
|
||||||
|
(skipInCI && process.env.CI) ||
|
||||||
|
(skipInProduction && process.env.NODE_ENV === 'production')) {
|
||||||
|
console.info('🐳 Docker/CI/Production build detected - skipping environment validation');
|
||||||
|
return { hasErrors: false, missingVariables: [] };
|
||||||
|
}
|
||||||
|
const missingVariables = [];
|
||||||
|
for (const [envKey, value] of Object.entries(requiredVars)) {
|
||||||
|
if (!value) {
|
||||||
|
console.error(`❌ Missing required environment variable: ${envKey}`);
|
||||||
|
missingVariables.push(envKey);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.info(`✅ ${envKey} is set`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (missingVariables.length > 0) {
|
||||||
|
console.error('');
|
||||||
|
console.error('❌ Build cannot continue with missing environment variables.');
|
||||||
|
console.error('Please check your .env file at the project root and ensure all required variables are set.');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.info('✅ All required environment variables are present');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
hasErrors: missingVariables.length > 0,
|
||||||
|
missingVariables,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Create a validate-env script for a package
|
||||||
|
export function createValidateEnvScript(requiredEnvVars) {
|
||||||
|
const envVarsObject = requiredEnvVars
|
||||||
|
.map(varName => ` ${varName}: process.env.${varName},`)
|
||||||
|
.join('\n');
|
||||||
|
return `#!/usr/bin/env node
|
||||||
|
|
||||||
|
// This script uses the shared env-utils to validate environment variables
|
||||||
|
import { loadRootEnv, validateEnv } from '@buster/env-utils';
|
||||||
|
|
||||||
|
// Load environment variables from root .env file
|
||||||
|
loadRootEnv();
|
||||||
|
|
||||||
|
// Define required environment variables for this package
|
||||||
|
const requiredEnv = {
|
||||||
|
${envVarsObject}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate environment variables
|
||||||
|
const { hasErrors } = validateEnv(requiredEnv);
|
||||||
|
|
||||||
|
if (hasErrors) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"name": "@buster/env-utils",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"dev": "tsc --watch",
|
||||||
|
"lint": "biome check"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@buster/typescript-config": "workspace:*",
|
||||||
|
"dotenv": "^16.6.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
import { config } from 'dotenv';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
|
||||||
|
// Get the directory of the current module
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
// Find the root of the monorepo (where turbo.json is)
|
||||||
|
function findMonorepoRoot(): string {
|
||||||
|
let currentDir = __dirname;
|
||||||
|
while (currentDir !== '/') {
|
||||||
|
if (existsSync(path.join(currentDir, 'turbo.json'))) {
|
||||||
|
return currentDir;
|
||||||
|
}
|
||||||
|
currentDir = path.dirname(currentDir);
|
||||||
|
}
|
||||||
|
throw new Error('Could not find monorepo root (turbo.json)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load environment variables from root .env file
|
||||||
|
export function loadRootEnv(): void {
|
||||||
|
const rootDir = findMonorepoRoot();
|
||||||
|
const envPath = path.join(rootDir, '.env');
|
||||||
|
|
||||||
|
config({ path: envPath });
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnvValidationResult {
|
||||||
|
hasErrors: boolean;
|
||||||
|
missingVariables: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnvValidationOptions {
|
||||||
|
skipInCI?: boolean;
|
||||||
|
skipInProduction?: boolean;
|
||||||
|
skipInDocker?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required environment variables
|
||||||
|
export function validateEnv(
|
||||||
|
requiredVars: Record<string, string | undefined>,
|
||||||
|
options: EnvValidationOptions = {}
|
||||||
|
): EnvValidationResult {
|
||||||
|
const {
|
||||||
|
skipInCI = true,
|
||||||
|
skipInProduction = true,
|
||||||
|
skipInDocker = true,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
console.info('🔍 Validating environment variables...');
|
||||||
|
|
||||||
|
// Skip validation in certain environments
|
||||||
|
if (
|
||||||
|
(skipInDocker && process.env.DOCKER_BUILD) ||
|
||||||
|
(skipInCI && process.env.CI) ||
|
||||||
|
(skipInProduction && process.env.NODE_ENV === 'production')
|
||||||
|
) {
|
||||||
|
console.info(
|
||||||
|
'🐳 Docker/CI/Production build detected - skipping environment validation'
|
||||||
|
);
|
||||||
|
return { hasErrors: false, missingVariables: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const missingVariables: string[] = [];
|
||||||
|
|
||||||
|
for (const [envKey, value] of Object.entries(requiredVars)) {
|
||||||
|
if (!value) {
|
||||||
|
console.error(`❌ Missing required environment variable: ${envKey}`);
|
||||||
|
missingVariables.push(envKey);
|
||||||
|
} else {
|
||||||
|
console.info(`✅ ${envKey} is set`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingVariables.length > 0) {
|
||||||
|
console.error('');
|
||||||
|
console.error('❌ Build cannot continue with missing environment variables.');
|
||||||
|
console.error('Please check your .env file at the project root and ensure all required variables are set.');
|
||||||
|
} else {
|
||||||
|
console.info('✅ All required environment variables are present');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasErrors: missingVariables.length > 0,
|
||||||
|
missingVariables,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a validate-env script for a package
|
||||||
|
export function createValidateEnvScript(requiredEnvVars: string[]): string {
|
||||||
|
const envVarsObject = requiredEnvVars
|
||||||
|
.map(varName => ` ${varName}: process.env.${varName},`)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
return `#!/usr/bin/env node
|
||||||
|
|
||||||
|
// This script uses the shared env-utils to validate environment variables
|
||||||
|
import { loadRootEnv, validateEnv } from '@buster/env-utils';
|
||||||
|
|
||||||
|
// Load environment variables from root .env file
|
||||||
|
loadRootEnv();
|
||||||
|
|
||||||
|
// Define required environment variables for this package
|
||||||
|
const requiredEnv = {
|
||||||
|
${envVarsObject}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate environment variables
|
||||||
|
const { hasErrors } = validateEnv(requiredEnv);
|
||||||
|
|
||||||
|
if (hasErrors) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"extends": "@buster/typescript-config/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "dist/.cache/tsbuildinfo.json",
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
|
@ -772,6 +772,9 @@ importers:
|
||||||
|
|
||||||
packages/database:
|
packages/database:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@buster/env-utils':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../env-utils
|
||||||
'@buster/typescript-config':
|
'@buster/typescript-config':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../typescript-config
|
version: link:../typescript-config
|
||||||
|
@ -797,6 +800,15 @@ importers:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 3.25.1
|
version: 3.25.1
|
||||||
|
|
||||||
|
packages/env-utils:
|
||||||
|
dependencies:
|
||||||
|
'@buster/typescript-config':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../typescript-config
|
||||||
|
dotenv:
|
||||||
|
specifier: ^16.6.1
|
||||||
|
version: 16.6.1
|
||||||
|
|
||||||
packages/rerank:
|
packages/rerank:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@buster/typescript-config':
|
'@buster/typescript-config':
|
||||||
|
@ -19179,8 +19191,8 @@ snapshots:
|
||||||
'@typescript-eslint/parser': 8.35.1(eslint@8.57.1)(typescript@5.8.3)
|
'@typescript-eslint/parser': 8.35.1(eslint@8.57.1)(typescript@5.8.3)
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1)
|
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
|
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
|
||||||
eslint-plugin-react: 7.37.5(eslint@8.57.1)
|
eslint-plugin-react: 7.37.5(eslint@8.57.1)
|
||||||
eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1)
|
eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1)
|
||||||
|
@ -19203,7 +19215,7 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1):
|
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nolyfill/is-core-module': 1.0.39
|
'@nolyfill/is-core-module': 1.0.39
|
||||||
debug: 4.4.1
|
debug: 4.4.1
|
||||||
|
@ -19214,22 +19226,22 @@ snapshots:
|
||||||
tinyglobby: 0.2.14
|
tinyglobby: 0.2.14
|
||||||
unrs-resolver: 1.11.0
|
unrs-resolver: 1.11.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
|
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@typescript-eslint/parser': 8.35.1(eslint@8.57.1)(typescript@5.8.3)
|
'@typescript-eslint/parser': 8.35.1(eslint@8.57.1)(typescript@5.8.3)
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1)
|
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
|
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rtsao/scc': 1.1.0
|
'@rtsao/scc': 1.1.0
|
||||||
array-includes: 3.1.9
|
array-includes: 3.1.9
|
||||||
|
@ -19240,7 +19252,7 @@ snapshots:
|
||||||
doctrine: 2.1.0
|
doctrine: 2.1.0
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.1(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
is-core-module: 2.16.1
|
is-core-module: 2.16.1
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
|
|
|
@ -240,9 +240,11 @@ async function main() {
|
||||||
console.log(`\n✅ ${config.type === 'package' ? 'Package' : 'App'} created successfully!`);
|
console.log(`\n✅ ${config.type === 'package' ? 'Package' : 'App'} created successfully!`);
|
||||||
console.log(`\n📋 Next steps:`);
|
console.log(`\n📋 Next steps:`);
|
||||||
console.log(` 1. cd ${config.type === 'package' ? 'packages' : 'apps'}/${config.name}`);
|
console.log(` 1. cd ${config.type === 'package' ? 'packages' : 'apps'}/${config.name}`);
|
||||||
console.log(` 2. Update the env.d.ts file with your environment variables`);
|
console.log(` 2. Update the env.d.ts file with your environment variable types`);
|
||||||
console.log(` 3. Add your source code in the src/ directory`);
|
console.log(` 3. Add any required env vars to scripts/validate-env.js`);
|
||||||
console.log(` 4. Run 'npm run build' to build the ${config.type}`);
|
console.log(` 4. Make sure required env vars are defined in the root .env file`);
|
||||||
|
console.log(` 5. Add your source code in the src/ directory`);
|
||||||
|
console.log(` 6. Run 'npm run build' to build the ${config.type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createPackageFiles(config: PackageConfig) {
|
async function createPackageFiles(config: PackageConfig) {
|
||||||
|
@ -281,7 +283,8 @@ async function createPackageFiles(config: PackageConfig) {
|
||||||
},
|
},
|
||||||
dependencies: {
|
dependencies: {
|
||||||
"@buster/typescript-config": "workspace:*",
|
"@buster/typescript-config": "workspace:*",
|
||||||
"@buster/vitest-config": "workspace:*"
|
"@buster/vitest-config": "workspace:*",
|
||||||
|
"@buster/env-utils": "workspace:*"
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -363,48 +366,26 @@ export const howdy = () => {
|
||||||
// Create a proper validate-env.js script
|
// Create a proper validate-env.js script
|
||||||
const validateEnv = `#!/usr/bin/env node
|
const validateEnv = `#!/usr/bin/env node
|
||||||
|
|
||||||
// Load environment variables from .env file
|
// This script uses the shared env-utils to validate environment variables
|
||||||
import { config } from 'dotenv';
|
import { loadRootEnv, validateEnv } from '@buster/env-utils';
|
||||||
config();
|
|
||||||
|
|
||||||
// Build-time environment validation
|
// Load environment variables from root .env file
|
||||||
|
loadRootEnv();
|
||||||
|
|
||||||
console.info('🔍 Validating environment variables...');
|
// Define required environment variables for this package
|
||||||
|
const requiredEnv = {
|
||||||
// Skip validation during Docker builds (environment variables are only available at runtime)
|
// NODE_ENV is optional - will default to 'development' if not set
|
||||||
if (process.env.DOCKER_BUILD || process.env.CI || process.env.NODE_ENV === 'production') {
|
// Add your required environment variables here:
|
||||||
console.info(
|
|
||||||
'🐳 Docker/CI build detected - skipping environment validation (will validate at runtime)'
|
|
||||||
);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const env = {
|
|
||||||
NODE_ENV: process.env.NODE_ENV || 'development',
|
|
||||||
// Add your required environment variables here
|
|
||||||
// DATABASE_URL: process.env.DATABASE_URL,
|
// DATABASE_URL: process.env.DATABASE_URL,
|
||||||
// API_KEY: process.env.API_KEY,
|
// API_KEY: process.env.API_KEY,
|
||||||
};
|
};
|
||||||
|
|
||||||
let hasErrors = false;
|
// Validate environment variables
|
||||||
|
const { hasErrors } = validateEnv(requiredEnv);
|
||||||
for (const [envKey, value] of Object.entries(env)) {
|
|
||||||
if (!value) {
|
|
||||||
console.error(\`❌ Missing required environment variable: \${envKey}\`);
|
|
||||||
hasErrors = true;
|
|
||||||
} else {
|
|
||||||
console.info(\`✅ \${envKey} is set\`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasErrors) {
|
if (hasErrors) {
|
||||||
console.error('');
|
|
||||||
console.error('❌ Build cannot continue with missing environment variables.');
|
|
||||||
console.error('Please check your .env file and ensure all required variables are set.');
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.info('✅ All required environment variables are present');
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
await writeFile(join(directory, "scripts", "validate-env.js"), validateEnv);
|
await writeFile(join(directory, "scripts", "validate-env.js"), validateEnv);
|
||||||
|
|
Loading…
Reference in New Issue