UNPKG

@daitanjs/development

Version:

Development utilities for environment management, Google APIs, and system information.

149 lines (146 loc) 5.55 kB
// development/src/environment.js import dotenv from 'dotenv'; import path from 'path'; import fs from 'fs'; import os from 'os'; import { DaitanConfigurationError, DaitanInvalidInputError } from '@daitanjs/error'; import chalk from 'chalk'; // --- SINGLETON PATTERN: Ensure environment is loaded only once --- let hasLoaded = false; // --- const getPreLoadEnvVar = (key, defaultValue = null) => { const value = process.env[key]; return value === undefined || value === null || String(value).trim() === '' ? defaultValue : String(value).trim(); }; export function loadEnvironmentFiles(options = {}) { // Prevent re-loading if it has already been done. if (hasLoaded && !options.forceReload) return; const { envPath, overrideDotenv = true, debugDotenv = false } = options; const pathsAttemptedThisCall = new Set(); const tryLoadSinglePath = (filePathToLoad, fileDescription) => { if (!filePathToLoad || typeof filePathToLoad !== 'string' || !filePathToLoad.trim()) { return; } const tildeExpandedPath = filePathToLoad.trim().replace(/^~/, os.homedir()); const resolvedPath = path.resolve(tildeExpandedPath); if (pathsAttemptedThisCall.has(resolvedPath)) return; pathsAttemptedThisCall.add(resolvedPath); if (!fs.existsSync(resolvedPath)) { if (debugDotenv) console.log(`[DaitanEnvLoader] File not found for ${fileDescription}: "${resolvedPath}"`); return; } if (debugDotenv) console.log(`[DaitanEnvLoader] Attempting to load .env file: "${resolvedPath}" (${fileDescription})`); dotenv.config({ path: resolvedPath, override: overrideDotenv, debug: debugDotenv }); }; const effectiveDebug = debugDotenv || getPreLoadEnvVar('DEBUG_DOTENV') === 'true'; if (effectiveDebug) console.log('[DaitanEnvLoader] Initiating .env file loading sequence...'); const nodeEnv = getPreLoadEnvVar('NODE_ENV'); const cwd = process.cwd(); const fileLoadOrder = [{ pathStr: getPreLoadEnvVar('DAITAN_GLOBAL_ENV_PATH'), description: 'Global DaitanJS Override via DAITAN_GLOBAL_ENV_PATH' }, { pathStr: envPath, description: 'Explicit path from function options' }, { pathStr: path.join(cwd, '.env'), description: 'Local Project (.env)' }, { pathStr: path.join(cwd, '.env.local'), description: 'Local Project Override (.env.local)' }, ...(nodeEnv ? [{ pathStr: path.join(cwd, `.env.${nodeEnv}`), description: `Local Project (.env.${nodeEnv})` }, { pathStr: path.join(cwd, `.env.${nodeEnv}.local`), description: `Local Project Override (.env.${nodeEnv}.local)` }] : [])]; fileLoadOrder.forEach(({ pathStr, description }) => { if (pathStr) { tryLoadSinglePath(pathStr, description); } }); hasLoaded = true; // Mark as loaded if (effectiveDebug) console.log('[DaitanEnvLoader] .env file loading sequence complete.'); } export const getEnvVariable = (key, defaultValue, options = {}) => { // --- ROBUSTNESS FIX: Ensure files are loaded before trying to get a variable --- if (!hasLoaded) { loadEnvironmentFiles(); } // --- const { isRequired = false, type = 'string', description = '' } = options; if (!key || typeof key !== 'string' || !key.trim()) { throw new DaitanInvalidInputError('Environment variable key must be a non-empty string.'); } const trimmedKey = key.trim(); const rawValue = process.env[trimmedKey]; const valueString = rawValue === undefined || rawValue === null ? '' : String(rawValue).trim(); const isMissingOrEmpty = valueString === ''; if (isMissingOrEmpty) { if (isRequired) { const errorMsg = `Missing required environment variable: ${trimmedKey}. Description: ${description || 'N/A'}`; if (process.env.DAITAN_ENV_VALIDATION_MODE === 'strict') { console.error(chalk.red.bold(`\nFATAL: ${errorMsg}\n`)); process.exit(1); } throw new DaitanConfigurationError(errorMsg, { key: trimmedKey }); } return defaultValue; } try { switch (type) { case 'number': const num = parseFloat(valueString); if (isNaN(num)) throw new Error(`Value "${valueString}" is not a valid number.`); return num; case 'boolean': const lowerValue = valueString.toLowerCase(); if (['true', '1', 'yes', 'on'].includes(lowerValue)) return true; if (['false', '0', 'no', 'off'].includes(lowerValue)) return false; throw new Error(`Value "${valueString}" is not a valid boolean.`); case 'array': return valueString.split(',').map(s => s.trim()).filter(Boolean); default: return valueString; } } catch (castError) { const errorMsg = `Environment variable ${trimmedKey} ("${valueString}") could not be cast to type "${type}": ${castError.message}`; if (isRequired) { if (process.env.DAITAN_ENV_VALIDATION_MODE === 'strict') { console.error(chalk.red.bold(`\nFATAL: ${errorMsg}\n`)); process.exit(1); } throw new DaitanConfigurationError(errorMsg); } return defaultValue; } }; export const getRequiredEnvVariable = (key, type = 'string', description = '') => getEnvVariable(key, undefined, { isRequired: true, type, description }); export const getOptionalEnvVariable = (key, defaultValue, options = {}) => getEnvVariable(key, defaultValue, { isRequired: false, ...options }); export const checkEnv = variableName => !!getOptionalEnvVariable(variableName); //# sourceMappingURL=environment.js.map