UNPKG

@elsikora/commitizen-plugin-commitlint-ai

Version:
179 lines (176 loc) 7.46 kB
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import chalk from 'chalk'; // Store config in project directory const CONFIG_DIR = "./.elsikora"; const CONFIG_FILE = join(CONFIG_DIR, "commitlint-ai.config.js"); // In-memory cache let llmConfig = null; // Track if we've already shown mode error // eslint-disable-next-line @elsikora-typescript/naming-convention let modeErrorShown = false; // Check for API keys in environment variables const getApiKeyFromEnvironment = (provider) => { try { if (typeof process === "undefined" || !process?.env) { return null; } if (provider === "openai") { return process.env.OPENAI_API_KEY ?? null; } else if (provider === "anthropic") { return process.env.ANTHROPIC_API_KEY ?? null; } } catch (error) { console.warn("Error accessing environment variables:", error); } return null; }; // Try to load config from file const loadConfigFromFile = () => { try { if (existsSync(CONFIG_FILE)) { // Check if there's an old JSON file and migrate it const oldJsonFile = join(CONFIG_DIR, "commitlint-ai.json"); if (existsSync(oldJsonFile)) { try { const oldConfigString = readFileSync(oldJsonFile, "utf8"); const oldConfig = JSON.parse(oldConfigString); // Save to the new JS format saveConfigToFile({ ...oldConfig, apiKey: "", }); return oldConfig; } catch { // Ignore errors with old file } } // Parse the ESM module format const configContent = readFileSync(CONFIG_FILE, "utf8"); try { // Use a safer approach than regex + JSON.parse // Execute the file as a JavaScript module using Node's module system // This is a workaround since we can't directly import a dynamic path in ESM // Simple approach: parse the JS object manually const objectPattern = /export\s+default\s+(\{[\s\S]*?\});/; const match = objectPattern.exec(configContent); if (match?.[1]) { // Extract the object text const objectText = match[1]; // Extract property assignments with a more robust approach const properties = {}; // Match each property in the format: key: value, // eslint-disable-next-line @elsikora-sonar/slow-regex const propertyRegex = /\s*(\w+)\s*:\s*["']?([^,"'}\s]+)["']?\s*,?/g; // eslint-disable-next-line @elsikora-typescript/typedef let propertyMatch; // eslint-disable-next-line @elsikora-typescript/no-unsafe-argument while ((propertyMatch = propertyRegex.exec(objectText)) !== null) { const [, key, value] = propertyMatch; // Remove quotes if present // eslint-disable-next-line @elsikora-sonar/anchor-precedence,@elsikora-typescript/no-unsafe-assignment,@elsikora-typescript/no-unsafe-member-access,@elsikora-typescript/no-unsafe-call const cleanValue = value.replaceAll(/^["']|["']$/g, ""); // eslint-disable-next-line @elsikora-typescript/no-unsafe-assignment,@elsikora-typescript/no-unsafe-member-access properties[key] = cleanValue; } // Validate mode if present (but only show the error once) if (properties.mode && properties.mode !== "auto" && properties.mode !== "manual") { if (!modeErrorShown) { // eslint-disable-next-line @elsikora-typescript/restrict-template-expressions console.log(chalk.yellow(`Invalid mode "${properties.mode}" in config. Valid values are "auto" or "manual". Using default mode.`)); modeErrorShown = true; } properties.mode = "auto"; } return properties; } return null; } catch (parseError) { console.warn("Error parsing config file:", parseError); return null; } } } catch (error) { console.warn("Error loading LLM config from file:", error); } return null; }; // Save config to file (without API key) const saveConfigToFile = (config) => { try { if (!existsSync(CONFIG_DIR)) { // eslint-disable-next-line @elsikora-typescript/naming-convention mkdirSync(CONFIG_DIR, { recursive: true }); } // Only store provider, model, and mode (not the API key) const storageConfig = { mode: config.mode, model: config.model, provider: config.provider, }; // Format as an ESM module with proper JS object format (no quotes around keys) // Always include the mode field, using 'auto' as default if not specified const jsContent = `export default { provider: ${JSON.stringify(storageConfig.provider)}, model: ${JSON.stringify(storageConfig.model)}, mode: ${JSON.stringify(storageConfig.mode ?? "auto")} };`; writeFileSync(CONFIG_FILE, jsContent, "utf8"); // Remove old JSON file if it exists const oldJsonFile = join(CONFIG_DIR, "commitlint-ai.json"); if (existsSync(oldJsonFile)) { try { // Use fs.unlink to delete the file - but we'll use writeFileSync with empty content instead // to avoid needing to import fs.unlink writeFileSync(oldJsonFile, "", "utf8"); } catch { // Ignore errors with old file deletion } } } catch (error) { console.warn("Error saving LLM config to file:", error); } }; const setLLMConfig = (config) => { llmConfig = config; if (config) { // For debugging console.log("Saving config:", JSON.stringify({ ...config, apiKey: "[REDACTED]" })); saveConfigToFile(config); } }; const getLLMConfig = () => { // If we already have a config in memory, return it if (llmConfig) { return llmConfig; } // Otherwise try to load from file const fileConfig = loadConfigFromFile(); if (fileConfig) { // Check if we have API key in environment const apiKey = getApiKeyFromEnvironment(fileConfig.provider); // We have both the saved config and an API key if (apiKey) { llmConfig = { ...fileConfig, apiKey, }; return llmConfig; } // Return the partial config (without API key) so we can ask for it return { ...fileConfig, apiKey: "", // Empty string signals that we need to ask for the key }; } return null; }; export { getLLMConfig, setLLMConfig }; //# sourceMappingURL=config.js.map