Files
flipstone-radar/server/config.ts

173 lines
4.5 KiB
TypeScript

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();
}
}
}