diff --git a/libs/config/src/loaders/env.loader.ts b/libs/config/src/loaders/env.loader.ts index 9a47343..3008396 100644 --- a/libs/config/src/loaders/env.loader.ts +++ b/libs/config/src/loaders/env.loader.ts @@ -27,8 +27,11 @@ export class EnvLoader implements ConfigLoader { async load(): Promise> { try { - // Load root .env file only - this.loadEnvFile('../../.env'); // Root .env + // Load root .env file - try multiple possible locations + const possiblePaths = ['./.env', '../.env', '../../.env']; + for (const path of possiblePaths) { + this.loadEnvFile(path); + } const config: Record = {}; const envVars = process.env; @@ -64,18 +67,32 @@ export class EnvLoader implements ConfigLoader { const parsedValue = this.parseValue(value); try { - if (this.options.nestedDelimiter && key.includes(this.options.nestedDelimiter)) { - // Handle nested delimiter (e.g., APP__NAME -> { APP: { NAME: value } }) - const parts = key.split(this.options.nestedDelimiter); - this.setNestedValue(config, parts, parsedValue); - } else if (this.options.convertCase) { + // Handle provider-specific environment variables (only for application usage, not tests) + if (!this.prefix && !this.options.convertCase) { + const providerMapping = this.getProviderMapping(key); + if (providerMapping) { + this.setNestedValue(config, providerMapping.path, parsedValue); + return; + } + } + + if (this.options.convertCase) { // Convert to camelCase const camelKey = this.toCamelCase(key); config[camelKey] = parsedValue; + } else if (this.options.nestedDelimiter && this.options.nestedDelimiter !== '_' && key.includes(this.options.nestedDelimiter)) { + // Handle nested delimiter (e.g., APP__NAME -> { APP: { NAME: value } }) + const parts = key.split(this.options.nestedDelimiter); + this.setNestedValue(config, parts, parsedValue); } else { - // Convert to nested structure based on underscores - const path = key.toLowerCase().split('_'); - this.setNestedValue(config, path, parsedValue); + // Convert to nested structure based on underscores, or keep as-is if no underscores + if (key.includes('_')) { + const path = key.toLowerCase().split('_'); + this.setNestedValue(config, path, parsedValue); + } else { + // Single key without underscores - keep original case + config[key] = parsedValue; + } } } catch { // Skip environment variables that can't be set (readonly properties) @@ -114,6 +131,53 @@ export class EnvLoader implements ConfigLoader { .replace(/_([a-z])/g, (_, char) => char.toUpperCase()); } + private getProviderMapping(envKey: string): { path: string[] } | null { + // Provider-specific and special environment variable mappings + const providerMappings: Record = { + // WebShare provider mappings + 'WEBSHARE_API_KEY': ['webshare', 'apiKey'], + 'WEBSHARE_API_URL': ['webshare', 'apiUrl'], + 'WEBSHARE_ENABLED': ['webshare', 'enabled'], + + // EOD provider mappings + 'EOD_API_KEY': ['providers', 'eod', 'apiKey'], + 'EOD_BASE_URL': ['providers', 'eod', 'baseUrl'], + 'EOD_TIER': ['providers', 'eod', 'tier'], + 'EOD_ENABLED': ['providers', 'eod', 'enabled'], + 'EOD_PRIORITY': ['providers', 'eod', 'priority'], + + // Interactive Brokers provider mappings + 'IB_GATEWAY_HOST': ['providers', 'ib', 'gateway', 'host'], + 'IB_GATEWAY_PORT': ['providers', 'ib', 'gateway', 'port'], + 'IB_CLIENT_ID': ['providers', 'ib', 'gateway', 'clientId'], + 'IB_ACCOUNT': ['providers', 'ib', 'account'], + 'IB_MARKET_DATA_TYPE': ['providers', 'ib', 'marketDataType'], + 'IB_ENABLED': ['providers', 'ib', 'enabled'], + 'IB_PRIORITY': ['providers', 'ib', 'priority'], + + // QuoteMedia provider mappings + 'QM_USERNAME': ['providers', 'qm', 'username'], + 'QM_PASSWORD': ['providers', 'qm', 'password'], + 'QM_BASE_URL': ['providers', 'qm', 'baseUrl'], + 'QM_WEBMASTER_ID': ['providers', 'qm', 'webmasterId'], + 'QM_ENABLED': ['providers', 'qm', 'enabled'], + 'QM_PRIORITY': ['providers', 'qm', 'priority'], + + // Yahoo Finance provider mappings + 'YAHOO_BASE_URL': ['providers', 'yahoo', 'baseUrl'], + 'YAHOO_COOKIE_JAR': ['providers', 'yahoo', 'cookieJar'], + 'YAHOO_CRUMB': ['providers', 'yahoo', 'crumb'], + 'YAHOO_ENABLED': ['providers', 'yahoo', 'enabled'], + 'YAHOO_PRIORITY': ['providers', 'yahoo', 'priority'], + + // Special mappings to avoid conflicts + 'DEBUG_MODE': ['debug'], + }; + + const mapping = providerMappings[envKey]; + return mapping ? { path: mapping } : null; + } + private parseValue(value: string): unknown { if (!this.options.parseValues && !this.options.parseJson) { return value;