index.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import { readFile } from "fs/promises"
  2. import { resolve } from "path"
  3. /**
  4. * Configuration for environment variable loading
  5. */
  6. export interface EnvLoaderConfig {
  7. /** Custom paths to search for .env files (relative to current working directory) */
  8. searchPaths?: string[]
  9. /** Whether to log when environment variables are loaded */
  10. verbose?: boolean
  11. /** Whether to override existing environment variables */
  12. override?: boolean
  13. }
  14. /**
  15. * Default search paths for .env files
  16. */
  17. const DEFAULT_ENV_PATHS = [
  18. './.env',
  19. '../.env',
  20. '../../.env',
  21. '../plugin/.env',
  22. '../../../.env'
  23. ]
  24. /**
  25. * Load environment variables from .env files
  26. * Searches multiple common locations for .env files and loads them into process.env
  27. *
  28. * @param config Configuration options
  29. * @returns Object containing loaded environment variables
  30. */
  31. export async function loadEnvVariables(config: EnvLoaderConfig = {}): Promise<Record<string, string>> {
  32. const {
  33. searchPaths = DEFAULT_ENV_PATHS,
  34. verbose = false,
  35. override = false
  36. } = config
  37. const loadedVars: Record<string, string> = {}
  38. for (const envPath of searchPaths) {
  39. try {
  40. const fullPath = resolve(envPath)
  41. const content = await readFile(fullPath, 'utf8')
  42. if (verbose) {
  43. console.log(`Checking .env file: ${envPath}`)
  44. }
  45. // Parse .env file content
  46. const lines = content.split('\n')
  47. for (const line of lines) {
  48. const trimmed = line.trim()
  49. if (trimmed && !trimmed.startsWith('#') && trimmed.includes('=')) {
  50. const [key, ...valueParts] = trimmed.split('=')
  51. const value = valueParts.join('=').trim()
  52. // Remove quotes if present
  53. const cleanValue = value.replace(/^["']|["']$/g, '')
  54. if (key && cleanValue && (override || !process.env[key])) {
  55. process.env[key] = cleanValue
  56. loadedVars[key] = cleanValue
  57. if (verbose) {
  58. console.log(`Loaded ${key} from ${envPath}`)
  59. }
  60. }
  61. }
  62. }
  63. } catch (error) {
  64. // File doesn't exist or can't be read, continue to next
  65. if (verbose) {
  66. console.log(`Could not read ${envPath}: ${error.message}`)
  67. }
  68. }
  69. }
  70. return loadedVars
  71. }
  72. /**
  73. * Get a specific environment variable with automatic .env file loading
  74. *
  75. * @param varName Name of the environment variable
  76. * @param config Configuration options
  77. * @returns The environment variable value or null if not found
  78. */
  79. export async function getEnvVariable(varName: string, config: EnvLoaderConfig = {}): Promise<string | null> {
  80. // First check if it's already in the environment
  81. let value = process.env[varName]
  82. if (!value) {
  83. // Try to load from .env files
  84. const loadedVars = await loadEnvVariables(config)
  85. value = loadedVars[varName] || process.env[varName]
  86. }
  87. return value || null
  88. }
  89. /**
  90. * Get a required environment variable with automatic .env file loading
  91. * Throws an error if the variable is not found
  92. *
  93. * @param varName Name of the environment variable
  94. * @param config Configuration options
  95. * @returns The environment variable value
  96. * @throws Error if the variable is not found
  97. */
  98. export async function getRequiredEnvVariable(varName: string, config: EnvLoaderConfig = {}): Promise<string> {
  99. const value = await getEnvVariable(varName, config)
  100. if (!value) {
  101. const searchPaths = config.searchPaths || DEFAULT_ENV_PATHS
  102. throw new Error(`${varName} not found. Please set it in your environment or .env file.
  103. To fix this:
  104. 1. Add to .env file: ${varName}=your_value_here
  105. 2. Or export it: export ${varName}=your_value_here
  106. Current working directory: ${process.cwd()}
  107. Searched paths: ${searchPaths.join(', ')}
  108. Environment variables available: ${Object.keys(process.env).filter(k => k.includes(varName.split('_')[0])).join(', ') || 'none matching'}`)
  109. }
  110. return value
  111. }
  112. /**
  113. * Load multiple required environment variables at once
  114. *
  115. * @param varNames Array of environment variable names
  116. * @param config Configuration options
  117. * @returns Object with variable names as keys and values as values
  118. * @throws Error if any variable is not found
  119. */
  120. export async function getRequiredEnvVariables(varNames: string[], config: EnvLoaderConfig = {}): Promise<Record<string, string>> {
  121. const result: Record<string, string> = {}
  122. // Load all .env files first
  123. await loadEnvVariables(config)
  124. // Check each required variable
  125. for (const varName of varNames) {
  126. const value = process.env[varName]
  127. if (!value) {
  128. throw new Error(`Required environment variable ${varName} not found. Please set it in your environment or .env file.`)
  129. }
  130. result[varName] = value
  131. }
  132. return result
  133. }
  134. /**
  135. * Utility function specifically for API keys
  136. *
  137. * @param apiKeyName Name of the API key environment variable
  138. * @param config Configuration options
  139. * @returns The API key value
  140. * @throws Error if the API key is not found
  141. */
  142. export async function getApiKey(apiKeyName: string, config: EnvLoaderConfig = {}): Promise<string> {
  143. return getRequiredEnvVariable(apiKeyName, config)
  144. }