done
This commit is contained in:
parent
caf1c5fcaf
commit
20b7180a43
5 changed files with 207 additions and 158 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import { ConfigLoader } from '../types';
|
||||
import { ConfigLoaderError } from '../errors';
|
||||
import { readFileSync } from 'fs';
|
||||
import { ConfigLoaderError } from '../errors';
|
||||
import { ConfigLoader } from '../types';
|
||||
|
||||
export interface EnvLoaderOptions {
|
||||
convertCase?: boolean;
|
||||
|
|
@ -21,7 +21,7 @@ export class EnvLoader implements ConfigLoader {
|
|||
parseJson: true,
|
||||
parseValues: true,
|
||||
nestedDelimiter: '_',
|
||||
...options
|
||||
...options,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ export class EnvLoader implements ConfigLoader {
|
|||
for (const path of possiblePaths) {
|
||||
this.loadEnvFile(path);
|
||||
}
|
||||
|
||||
|
||||
const config: Record<string, unknown> = {};
|
||||
const envVars = process.env;
|
||||
|
||||
|
|
@ -41,9 +41,7 @@ export class EnvLoader implements ConfigLoader {
|
|||
continue;
|
||||
}
|
||||
|
||||
const configKey = this.prefix
|
||||
? key.slice(this.prefix.length)
|
||||
: key;
|
||||
const configKey = this.prefix ? key.slice(this.prefix.length) : key;
|
||||
|
||||
if (!this.options.convertCase && !this.options.nestedDelimiter) {
|
||||
// Simple case - just keep the key as is
|
||||
|
|
@ -56,10 +54,7 @@ export class EnvLoader implements ConfigLoader {
|
|||
|
||||
return config;
|
||||
} catch (error) {
|
||||
throw new ConfigLoaderError(
|
||||
`Failed to load environment variables: ${error}`,
|
||||
'EnvLoader'
|
||||
);
|
||||
throw new ConfigLoaderError(`Failed to load environment variables: ${error}`, 'EnvLoader');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +75,11 @@ export class EnvLoader implements ConfigLoader {
|
|||
// Convert to camelCase
|
||||
const camelKey = this.toCamelCase(key);
|
||||
config[camelKey] = parsedValue;
|
||||
} else if (this.options.nestedDelimiter && this.options.nestedDelimiter !== '_' && key.includes(this.options.nestedDelimiter)) {
|
||||
} 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);
|
||||
|
|
@ -108,7 +107,7 @@ export class EnvLoader implements ConfigLoader {
|
|||
if (!lastKey) {
|
||||
return false; // This should never happen due to length check above
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const target = path.reduce((acc, key) => {
|
||||
if (!acc[key] || typeof acc[key] !== 'object') {
|
||||
|
|
@ -116,7 +115,7 @@ export class EnvLoader implements ConfigLoader {
|
|||
}
|
||||
return acc[key] as Record<string, unknown>;
|
||||
}, obj);
|
||||
|
||||
|
||||
(target as Record<string, unknown>)[lastKey] = value;
|
||||
return true;
|
||||
} catch {
|
||||
|
|
@ -126,63 +125,61 @@ export class EnvLoader implements ConfigLoader {
|
|||
}
|
||||
|
||||
private toCamelCase(str: string): string {
|
||||
return str
|
||||
.toLowerCase()
|
||||
.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
|
||||
return str.toLowerCase().replace(/_([a-z])/g, (_, char) => char.toUpperCase());
|
||||
}
|
||||
|
||||
private getProviderMapping(envKey: string): { path: string[] } | null {
|
||||
// Provider-specific and special environment variable mappings
|
||||
const providerMappings: Record<string, string[]> = {
|
||||
// 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'],
|
||||
|
||||
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'],
|
||||
|
||||
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'],
|
||||
|
||||
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'],
|
||||
|
||||
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'],
|
||||
|
||||
// General application config mappings
|
||||
'NAME': ['name'],
|
||||
'VERSION': ['version'],
|
||||
|
||||
NAME: ['name'],
|
||||
VERSION: ['version'],
|
||||
|
||||
// Log mappings (using LOG_ prefix for all)
|
||||
'LOG_LEVEL': ['log', 'level'],
|
||||
'LOG_FORMAT': ['log', 'format'],
|
||||
'LOG_LOKI_ENABLED': ['log', 'loki', 'enabled'],
|
||||
'LOG_LOKI_HOST': ['log', 'loki', 'host'],
|
||||
'LOG_LOKI_PORT': ['log', 'loki', 'port'],
|
||||
|
||||
LOG_LEVEL: ['log', 'level'],
|
||||
LOG_FORMAT: ['log', 'format'],
|
||||
LOG_LOKI_ENABLED: ['log', 'loki', 'enabled'],
|
||||
LOG_LOKI_HOST: ['log', 'loki', 'host'],
|
||||
LOG_LOKI_PORT: ['log', 'loki', 'port'],
|
||||
|
||||
// Special mappings to avoid conflicts
|
||||
'DEBUG_MODE': ['debug'],
|
||||
DEBUG_MODE: ['debug'],
|
||||
};
|
||||
|
||||
const mapping = providerMappings[envKey];
|
||||
|
|
@ -208,16 +205,26 @@ export class EnvLoader implements ConfigLoader {
|
|||
}
|
||||
|
||||
// Handle booleans
|
||||
if (value.toLowerCase() === 'true') {return true;}
|
||||
if (value.toLowerCase() === 'false') {return false;}
|
||||
if (value.toLowerCase() === 'true') {
|
||||
return true;
|
||||
}
|
||||
if (value.toLowerCase() === 'false') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle numbers
|
||||
const num = Number(value);
|
||||
if (!isNaN(num) && value !== '') {return num;}
|
||||
if (!isNaN(num) && value !== '') {
|
||||
return num;
|
||||
}
|
||||
|
||||
// Handle null/undefined
|
||||
if (value.toLowerCase() === 'null') {return null;}
|
||||
if (value.toLowerCase() === 'undefined') {return undefined;}
|
||||
if (value.toLowerCase() === 'null') {
|
||||
return null;
|
||||
}
|
||||
if (value.toLowerCase() === 'undefined') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Return as string
|
||||
return value;
|
||||
|
|
@ -227,27 +234,29 @@ export class EnvLoader implements ConfigLoader {
|
|||
try {
|
||||
const envContent = readFileSync(filePath, 'utf-8');
|
||||
const lines = envContent.split('\n');
|
||||
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) {
|
||||
continue; // Skip empty lines and comments
|
||||
}
|
||||
|
||||
|
||||
const equalIndex = trimmed.indexOf('=');
|
||||
if (equalIndex === -1) {
|
||||
continue; // Skip lines without =
|
||||
}
|
||||
|
||||
|
||||
const key = trimmed.substring(0, equalIndex).trim();
|
||||
let value = trimmed.substring(equalIndex + 1).trim();
|
||||
|
||||
|
||||
// Remove surrounding quotes if present
|
||||
if ((value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))) {
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
|
||||
|
||||
// Only set if not already set (allows override precedence)
|
||||
if (!(key in process.env)) {
|
||||
process.env[key] = value;
|
||||
|
|
@ -257,8 +266,11 @@ export class EnvLoader implements ConfigLoader {
|
|||
// File not found is not an error (env files are optional)
|
||||
if (error && typeof error === 'object' && 'code' in error && error.code !== 'ENOENT') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`Warning: Could not load env file ${filePath}:`, error instanceof Error ? error.message : String(error));
|
||||
console.warn(
|
||||
`Warning: Could not load env file ${filePath}:`,
|
||||
error instanceof Error ? error.message : String(error)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue