import fs from "node:fs/promises"; import readline from "node:readline"; import JSON5 from "json5"; import path from "node:path"; import { CONFIG_FILE, DEFAULT_CONFIG, HOME_DIR, PLUGINS_DIR, } from "../constants"; import { cleanupLogFiles } from "./logCleanup"; // Function to interpolate environment variables in config values const interpolateEnvVars = (obj: any): any => { if (typeof obj === "string") { // Replace $VAR_NAME or ${VAR_NAME} with environment variable values return obj.replace(/\$\{([^}]+)\}|\$([A-Z_][A-Z0-9_]*)/g, (match, braced, unbraced) => { const varName = braced || unbraced; return process.env[varName] || match; // Keep original if env var doesn't exist }); } else if (Array.isArray(obj)) { return obj.map(interpolateEnvVars); } else if (obj !== null && typeof obj === "object") { const result: any = {}; for (const [key, value] of Object.entries(obj)) { result[key] = interpolateEnvVars(value); } return result; } return obj; }; const ensureDir = async (dir_path: string) => { try { await fs.access(dir_path); } catch { await fs.mkdir(dir_path, { recursive: true }); } }; export const initDir = async () => { await ensureDir(HOME_DIR); await ensureDir(PLUGINS_DIR); await ensureDir(path.join(HOME_DIR, "logs")); }; const createReadline = () => { return readline.createInterface({ input: process.stdin, output: process.stdout, }); }; const question = (query: string): Promise => { return new Promise((resolve) => { const rl = createReadline(); rl.question(query, (answer) => { rl.close(); resolve(answer); }); }); }; const confirm = async (query: string): Promise => { const answer = await question(query); return answer.toLowerCase() !== "n"; }; export const readConfigFile = async () => { try { const config = await fs.readFile(CONFIG_FILE, "utf-8"); try { // Try to parse with JSON5 first (which also supports standard JSON) const parsedConfig = JSON5.parse(config); // Interpolate environment variables in the parsed config return interpolateEnvVars(parsedConfig); } catch (parseError) { console.error(`Failed to parse config file at ${CONFIG_FILE}`); console.error("Error details:", (parseError as Error).message); console.error("Please check your config file syntax."); process.exit(1); } } catch (readError: any) { if (readError.code === "ENOENT") { // Config file doesn't exist, prompt user for initial setup try { // Initialize directories await initDir(); // Backup existing config file if it exists const backupPath = await backupConfigFile(); if (backupPath) { console.log( `Backed up existing configuration file to ${backupPath}` ); } const config = { PORT: 3456, Providers: [], Router: {}, } // Create a minimal default config file await writeConfigFile(config); console.log( "Created minimal default configuration file at ~/.claude-code-router/config.json" ); console.log( "Please edit this file with your actual configuration." ); return config } catch (error: any) { console.error( "Failed to create default configuration:", error.message ); process.exit(1); } } else { console.error(`Failed to read config file at ${CONFIG_FILE}`); console.error("Error details:", readError.message); process.exit(1); } } }; export const backupConfigFile = async () => { try { if (await fs.access(CONFIG_FILE).then(() => true).catch(() => false)) { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupPath = `${CONFIG_FILE}.${timestamp}.bak`; await fs.copyFile(CONFIG_FILE, backupPath); // Clean up old backups, keeping only the 3 most recent try { const configDir = path.dirname(CONFIG_FILE); const configFileName = path.basename(CONFIG_FILE); const files = await fs.readdir(configDir); // Find all backup files for this config const backupFiles = files .filter(file => file.startsWith(configFileName) && file.endsWith('.bak')) .sort() .reverse(); // Sort in descending order (newest first) // Delete all but the 3 most recent backups if (backupFiles.length > 3) { for (let i = 3; i < backupFiles.length; i++) { const oldBackupPath = path.join(configDir, backupFiles[i]); await fs.unlink(oldBackupPath); } } } catch (cleanupError) { console.warn("Failed to clean up old backups:", cleanupError); } return backupPath; } } catch (error) { console.error("Failed to backup config file:", error); } return null; }; export const writeConfigFile = async (config: any) => { await ensureDir(HOME_DIR); const configWithComment = `${JSON.stringify(config, null, 2)}`; await fs.writeFile(CONFIG_FILE, configWithComment); }; export const initConfig = async () => { const config = await readConfigFile(); Object.assign(process.env, config); return config; }; // 导出日志清理函数 export { cleanupLogFiles }; // 导出更新功能 export { checkForUpdates, performUpdate } from "./update";