remcode
Version:
Turn your AI assistant into a codebase expert. Intelligent code analysis, semantic search, and software engineering guidance through MCP integration.
316 lines (315 loc) • 11.3 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadConfig = loadConfig;
exports.saveConfig = saveConfig;
exports.getConfigValue = getConfigValue;
exports.validateConfig = validateConfig;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const yaml = __importStar(require("yaml"));
const dotenv = __importStar(require("dotenv"));
const logger_1 = require("./logger");
const logger = (0, logger_1.getLogger)('Config');
// Load environment variables
dotenv.config();
/**
* Default configuration
*/
const defaultConfig = {
ignore: ['node_modules', 'dist', 'build', '.git', '*.min.js'],
analysis: {
depth: 2,
quality: {
enabled: true,
complexityThreshold: 10
},
dependencies: {
enabled: true,
includeExternal: true
}
},
vectorization: {
chunking: {
moduleLevelSize: 750,
functionLevelSize: 150,
overlapFactor: 0.2
},
embedding: {
model: 'graphcodebert',
fallbackModel: 'codebert',
batchSize: 16
},
storage: {
provider: 'pinecone',
indexes: {
moduleName: '{name}-module',
functionName: '{name}-function'
},
pinecone: {
environment: 'gcp-starter',
namespace: 'default'
}
}
},
server: {
port: 3000,
host: 'localhost'
}
};
/**
* Load configuration from a file or use default configuration
*/
function loadConfig(configPath) {
logger.debug(`Loading configuration${configPath ? ` from ${configPath}` : ''}`);
// Final config will combine: default config + file config + env vars
let fileConfig = {};
// Step 1: Find config file if not specified
if (!configPath) {
// Look for config files in the current directory
const possibleConfigPaths = [
'.remcode.yml',
'.remcode.yaml',
'.remcode.json',
'remcode.config.js'
];
for (const possiblePath of possibleConfigPaths) {
if (fs.existsSync(possiblePath)) {
configPath = possiblePath;
logger.debug(`Found config file: ${possiblePath}`);
break;
}
}
}
// Step 2: Load config from file if it exists
if (configPath && fs.existsSync(configPath)) {
try {
const ext = path.extname(configPath).toLowerCase();
if (ext === '.json') {
const content = fs.readFileSync(configPath, 'utf8');
fileConfig = JSON.parse(content);
logger.debug('Loaded configuration from JSON file');
}
else if (ext === '.yml' || ext === '.yaml') {
const content = fs.readFileSync(configPath, 'utf8');
fileConfig = yaml.parse(content);
logger.debug('Loaded configuration from YAML file');
}
else if (ext === '.js') {
// Note: This works for CommonJS modules, but not for ES modules
fileConfig = require(path.resolve(process.cwd(), configPath));
logger.debug('Loaded configuration from JS file');
}
else {
logger.warn(`Unsupported config file format: ${ext}`);
}
}
catch (error) {
logger.warn(`Failed to load config file: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
else if (configPath) {
logger.warn(`Config file not found: ${configPath}`);
}
else {
logger.info('No config file found, using default configuration');
}
// Step 3: Merge with environment variables
const envConfig = loadEnvironmentConfig();
// Step 4: Merge everything, with environment variables taking precedence
const mergedConfig = deepMerge(defaultConfig, fileConfig);
const finalConfig = deepMerge(mergedConfig, envConfig);
logger.debug('Configuration loaded successfully');
return finalConfig;
}
/**
* Load configuration from environment variables
*/
function loadEnvironmentConfig() {
const envConfig = {};
// Process Pinecone-related environment variables
if (process.env.REMCODE_PINECONE_API_KEY ||
process.env.REMCODE_PINECONE_ENVIRONMENT ||
process.env.REMCODE_PINECONE_NAMESPACE) {
// Create a complete vectorization object to avoid TypeScript errors
envConfig.vectorization = {
chunking: {
moduleLevelSize: 750,
functionLevelSize: 150,
overlapFactor: 0.2
},
embedding: {
model: 'graphcodebert',
fallbackModel: 'codebert',
batchSize: 16
},
storage: {
provider: 'pinecone',
indexes: {
moduleName: '{name}-module',
functionName: '{name}-function'
},
pinecone: {}
}
};
// Now we can safely assign to these properties
if (process.env.REMCODE_PINECONE_API_KEY) {
envConfig.vectorization.storage.pinecone = {
...envConfig.vectorization.storage.pinecone,
apiKey: process.env.REMCODE_PINECONE_API_KEY
};
}
if (process.env.REMCODE_PINECONE_ENVIRONMENT) {
envConfig.vectorization.storage.pinecone = {
...envConfig.vectorization.storage.pinecone,
environment: process.env.REMCODE_PINECONE_ENVIRONMENT
};
}
if (process.env.REMCODE_PINECONE_NAMESPACE) {
envConfig.vectorization.storage.pinecone = {
...envConfig.vectorization.storage.pinecone,
namespace: process.env.REMCODE_PINECONE_NAMESPACE
};
}
}
// Process GitHub-related environment variables
if (process.env.GITHUB_TOKEN || process.env.REMCODE_GITHUB_TOKEN) {
envConfig.github = {
token: process.env.GITHUB_TOKEN || process.env.REMCODE_GITHUB_TOKEN
};
}
// Process server-related environment variables
if (process.env.REMCODE_SERVER_PORT || process.env.REMCODE_SERVER_HOST) {
envConfig.server = {
port: process.env.REMCODE_SERVER_PORT ? parseInt(process.env.REMCODE_SERVER_PORT, 10) : 3000,
host: process.env.REMCODE_SERVER_HOST || 'localhost'
};
}
if (Object.keys(envConfig).length > 0) {
logger.debug('Loaded configuration from environment variables');
}
return envConfig;
}
/**
* Save configuration to a file
*/
function saveConfig(config, configPath) {
logger.debug(`Saving configuration to ${configPath}`);
try {
// Ensure directory exists
const dir = path.dirname(configPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const ext = path.extname(configPath).toLowerCase();
let content;
if (ext === '.json') {
content = JSON.stringify(config, null, 2);
}
else if (ext === '.yml' || ext === '.yaml') {
content = yaml.stringify(config);
}
else {
throw new Error(`Unsupported config file format: ${ext}`);
}
fs.writeFileSync(configPath, content, 'utf8');
logger.info(`Configuration saved to ${configPath}`);
}
catch (error) {
const errorMessage = `Failed to save config file: ${error instanceof Error ? error.message : 'Unknown error'}`;
logger.error(errorMessage);
throw new Error(errorMessage);
}
}
/**
* Get a specific configuration value by path
* Example: getConfigValue(config, 'vectorization.storage.provider')
*/
function getConfigValue(config, path, defaultValue) {
const parts = path.split('.');
let current = config;
for (const part of parts) {
if (current === undefined || current === null) {
return defaultValue;
}
current = current[part];
}
return current !== undefined ? current : defaultValue;
}
/**
* Validate configuration against schema
*/
function validateConfig(config) {
const errors = [];
// Check required fields
if (!config.vectorization) {
errors.push('Missing vectorization configuration');
}
else {
if (!config.vectorization.storage) {
errors.push('Missing vectorization.storage configuration');
}
else if (!config.vectorization.storage.provider) {
errors.push('Missing vectorization.storage.provider configuration');
}
else if (config.vectorization.storage.provider === 'pinecone') {
if (!config.vectorization.storage.pinecone?.apiKey && !process.env.REMCODE_PINECONE_API_KEY && !process.env.PINECONE_API_KEY) {
errors.push('Missing Pinecone API key. Set it in config or as REMCODE_PINECONE_API_KEY environment variable');
}
}
}
return { valid: errors.length === 0, errors };
}
/**
* Deep merge objects
*/
function deepMerge(target, source) {
if (!source)
return target;
if (typeof target !== 'object' || typeof source !== 'object')
return source;
const output = { ...target };
Object.keys(source).forEach(key => {
const value = source[key];
if (typeof value === 'object' && value !== null && !Array.isArray(value) &&
typeof output[key] === 'object' && output[key] !== null && !Array.isArray(output[key])) {
output[key] = deepMerge(output[key], value);
}
else {
output[key] = value;
}
});
return output;
}
;