import { readFileSync } from 'fs'; import { join } from 'path'; import chokidar from 'chokidar'; import { EventEmitter } from 'events'; export interface Repository { owner: string; name: string; token?: string; } export interface Config { github: { token: string; repositories: Repository[]; }; server: { port: number; host: string; }; cache?: { timeoutSeconds?: number; }; } const defaultConfig: Config = { github: { token: process.env.GITHUB_TOKEN || '', repositories: [] }, server: { port: parseInt(process.env.PORT || '3001'), host: process.env.HOST || '0.0.0.0' }, cache: { timeoutSeconds: 300 } }; export function loadConfig(): Config { const configPath = join(process.cwd(), 'config.json'); try { const configFile = readFileSync(configPath, 'utf8'); const fileConfig = JSON.parse(configFile); return { github: { token: fileConfig.github?.token || process.env.GITHUB_TOKEN || '', repositories: fileConfig.github?.repositories || [] }, server: { port: fileConfig.server?.port || parseInt(process.env.PORT || '3001'), host: fileConfig.server?.host || process.env.HOST || '0.0.0.0' }, cache: { timeoutSeconds: fileConfig.cache?.timeoutSeconds || 300 } }; } catch (error) { console.log('Config file not found, using environment variables and defaults'); return defaultConfig; } } // Legacy function for backward compatibility export function createConfigWatcher(): ConfigWatcher { return new ConfigWatcher(); } export function validateConfig(config: Config): void { if (!config.github.token) { throw new Error('GitHub token is required. Set GITHUB_TOKEN environment variable or add it to config.json'); } if (!config.github.repositories.length) { throw new Error('At least one repository is required in config.json'); } for (const repo of config.github.repositories) { if (!repo.owner || !repo.name) { throw new Error(`Invalid repository configuration: ${JSON.stringify(repo)}`); } } } export class ConfigWatcher extends EventEmitter { private config: Config; private configPath: string; private watcher?: chokidar.FSWatcher; constructor() { super(); this.configPath = join(process.cwd(), 'config.json'); this.config = this.loadConfigSync(); this.startWatching(); } private loadConfigSync(): Config { try { const configFile = readFileSync(this.configPath, 'utf8'); const fileConfig = JSON.parse(configFile); const config = { github: { token: fileConfig.github?.token || process.env.GITHUB_TOKEN || '', repositories: fileConfig.github?.repositories || [] }, server: { port: fileConfig.server?.port || parseInt(process.env.PORT || '3001'), host: fileConfig.server?.host || process.env.HOST || '0.0.0.0' }, cache: { timeoutSeconds: fileConfig.cache?.timeoutSeconds || 300 } }; validateConfig(config); return config; } catch (error) { console.log('Config file not found or invalid, using environment variables and defaults'); const config = { github: { token: process.env.GITHUB_TOKEN || '', repositories: [] }, server: { port: parseInt(process.env.PORT || '3001'), host: process.env.HOST || '0.0.0.0' }, cache: { timeoutSeconds: 300 } }; return config; } } private startWatching(): void { this.watcher = chokidar.watch(this.configPath, { ignored: /(^|[/\\])\../, // ignore dotfiles persistent: true }); this.watcher.on('change', () => { console.log('📁 Config file changed, reloading...'); try { const newConfig = this.loadConfigSync(); this.config = newConfig; this.emit('configChanged', newConfig); console.log('✅ Config reloaded successfully'); console.log(`📊 Now monitoring ${newConfig.github.repositories.length} repositories`); } catch (error) { console.error('❌ Failed to reload config:', error); this.emit('configError', error); } }); this.watcher.on('error', (error) => { console.error('❌ Config watcher error:', error); }); } public getConfig(): Config { return this.config; } public close(): void { if (this.watcher) { this.watcher.close(); } } }