Add centralized environment variable management with env-utils package

Co-authored-by: natemkelley <natemkelley@gmail.com>
This commit is contained in:
Cursor Agent 2025-07-21 21:14:50 +00:00
parent ae96e2f8b1
commit c1c7b37942
17 changed files with 502 additions and 140 deletions

View File

@ -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=

View File

@ -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

View File

@ -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:*",

View File

@ -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');

View File

@ -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:",

View File

@ -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');

View File

@ -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[] }`

View File

@ -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

13
packages/env-utils/dist/index.d.ts vendored Normal file
View File

@ -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

View File

@ -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"}

84
packages/env-utils/dist/index.js vendored Normal file
View File

@ -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);
}
`;
}

View File

@ -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"
}
}

View File

@ -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);
}
`;
}

View File

@ -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"]
}

View File

@ -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

View File

@ -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);