All files / src config.js

0% Statements 0/38
0% Branches 0/26
0% Functions 0/8
0% Lines 0/38

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182                                                                                                                                                                                                                                                                                                                                                                           
/**
 * ConfigService - Loads and provides access to environment variables
 *
 * This service loads environment variables from .env files using dotenv,
 * and provides centralized access to configuration values for the application.
 */
 
import dotenv from "dotenv";
import logger from "./utils/logger.js";
import path from "path";
import * as git from "isomorphic-git";
import { promises as fs } from "fs";
 
// Load environment variables from .env file
dotenv.config();
 
/**
 * Validates the LOG_LEVEL environment variable
 * @param {string} level - The log level to validate
 * @returns {string} - Valid log level or default 'info'
 */
const validateLogLevel = (level) => {
  const validLevels = ["debug", "info", "warn", "error"];
  if (level && validLevels.includes(level.toLowerCase())) {
    return level.toLowerCase();
  }
  return "info"; // Default log level
};
 
/**
 * Parses the TREE_SITTER_LANGUAGES environment variable
 * @param {string} languages - Comma-separated list of languages
 * @returns {string[]} - Array of language names
 */
const parseTreeSitterLanguages = (languages) => {
  if (!languages || typeof languages !== "string") {
    // Default to javascript, python, typescript if not set
    return ["javascript", "python", "typescript"];
  }
 
  // Parse comma-separated string into an array and trim each value
  return languages
    .split(",")
    .map((lang) => lang.trim())
    .filter((lang) => lang.length > 0);
};
 
/**
 * Parses the MAX_TEXT_FILE_SIZE_MB environment variable
 * @param {string} size - Size in MB
 * @returns {number} - Size in bytes
 */
const parseMaxTextFileSize = (size) => {
  // Try to parse the size as a number
  const parsed = parseFloat(size);
 
  // Check if parsing was successful and the value is positive
  if (!isNaN(parsed) && parsed > 0) {
    // Convert MB to bytes
    return parsed * 1024 * 1024;
  }
 
  // Default to 5MB if not set or invalid
  return 5 * 1024 * 1024;
};
 
/**
 * Determines the project path using current working directory or environment variable
 * @returns {Object} - Object containing path and source
 */
const determineProjectPath = () => {
  // First try to use current working directory
  const cwd = process.cwd();
 
  // If for some reason cwd is not available (unlikely), try environment variable
  if (!cwd) {
    const envProjectPath = process.env.PROJECT_PATH;
    if (envProjectPath && envProjectPath.trim() !== "") {
      return {
        path: envProjectPath,
        source: "environment variable",
      };
    }
  }
 
  return {
    path: cwd,
    source: "current working directory",
  };
};
 
/**
 * Validates if the given path is a Git repository
 * @param {string} projectPath - Path to validate
 * @returns {Promise<Object>} - Object containing validation result
 */
const validateGitRepository = async (projectPath) => {
  try {
    // Attempt to resolve HEAD ref, which should exist in any valid Git repository
    await git.resolveRef({ fs, dir: projectPath, ref: "HEAD" });
 
    // If we got here, it's a valid Git repository
    logger.info(`PROJECT_PATH validated as Git repository: ${projectPath}`);
 
    return {
      isValid: true,
      error: null,
    };
  } catch (error) {
    // Failed to resolve HEAD, likely not a Git repository
    logger.error(
      `PROJECT_PATH validation failed: ${projectPath} is not a valid Git repository`,
      {
        error: error.message,
        stack: error.stack,
      }
    );
 
    return {
      isValid: false,
      error: error,
    };
  }
};
 
// Determine the project path
const projectPathInfo = determineProjectPath();
 
/**
 * Configuration object with environment variables
 */
const config = {
  // TursoDB connection settings
  TURSO_DATABASE_URL: process.env.TURSO_DATABASE_URL,
  TURSO_AUTH_TOKEN: process.env.TURSO_AUTH_TOKEN,
 
  // Project path settings
  PROJECT_PATH: projectPathInfo.path,
 
  // Logging configuration
  LOG_LEVEL: validateLogLevel(process.env.LOG_LEVEL),
 
  // Indexing configuration
  MAX_TEXT_FILE_SIZE: parseMaxTextFileSize(process.env.MAX_TEXT_FILE_SIZE_MB),
  MAX_TEXT_FILE_SIZE_MB: parseFloat(process.env.MAX_TEXT_FILE_SIZE_MB) || 5, // Original value for reference
  TREE_SITTER_LANGUAGES: parseTreeSitterLanguages(
    process.env.TREE_SITTER_LANGUAGES
  ),
 
  // Git repository validation function
  validateGitRepository: async () => {
    return await validateGitRepository(config.PROJECT_PATH);
  },
};
 
// Log configuration (excluding sensitive information)
logger.info("Configuration loaded", {
  LOG_LEVEL: config.LOG_LEVEL,
  TURSO_DATABASE_URL: config.TURSO_DATABASE_URL ? "(set)" : "(not set)",
  TURSO_AUTH_TOKEN: config.TURSO_AUTH_TOKEN ? "(set)" : "(not set)",
  MAX_TEXT_FILE_SIZE_MB: config.MAX_TEXT_FILE_SIZE_MB,
  MAX_TEXT_FILE_SIZE: config.MAX_TEXT_FILE_SIZE,
  TREE_SITTER_LANGUAGES: config.TREE_SITTER_LANGUAGES,
});
 
// Log PROJECT_PATH resolution
logger.info(`PROJECT_PATH resolved to: ${config.PROJECT_PATH}`, {
  source: projectPathInfo.source,
  precedence: "current working directory takes precedence",
});
 
// Log sensitive information only at debug level
if (config.LOG_LEVEL === "debug") {
  logger.debug("Debug configuration details", {
    TURSO_DATABASE_URL: config.TURSO_DATABASE_URL,
    // Still don't log the actual token value, just indicate if it exists
    TURSO_AUTH_TOKEN_SET: Boolean(config.TURSO_AUTH_TOKEN),
  });
}
 
export default config;