UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

404 lines 16 kB
"use strict"; 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.configManager = exports.ConfigManager = void 0; const events_1 = require("events"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const os = __importStar(require("os")); const errors_1 = require("../utils/errors"); class ConfigManager extends events_1.EventEmitter { constructor(options = {}) { super(); this.lastSaveTime = 0; this.options = { configDir: options.configDir || path.join(os.homedir(), '.polyv-cli'), autoSave: options.autoSave ?? true, backupConfig: options.backupConfig ?? true, watchFiles: options.watchFiles ?? true, }; this.configDir = this.options.configDir; this.configFile = path.join(this.configDir, 'monitoring.json'); this.backupDir = path.join(this.configDir, 'backups'); this.config = this.getDefaultConfig(); this.ensureDirectories(); if (this.options.watchFiles) { this.setupFileWatcher(); } } async load() { try { if (fs.existsSync(this.configFile)) { const fileContent = await fs.promises.readFile(this.configFile, 'utf8'); const loadedConfig = JSON.parse(fileContent); const validation = this.validate(loadedConfig); if (!validation.valid) { throw new errors_1.ConfigurationError(`Invalid configuration: ${validation.errors.join(', ')}`); } this.config = this.mergeWithDefaults(loadedConfig); await this.migrateIfNeeded(); this.emit('config:loaded', this.config); return this.config; } else { await this.save(this.config); this.emit('config:created', this.config); return this.config; } } catch (error) { if (error instanceof errors_1.ConfigurationError) { throw error; } throw new errors_1.ConfigurationError(`Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`); } } async save(config) { const configToSave = config || this.config; try { const validation = this.validate(configToSave); if (!validation.valid) { throw new errors_1.ConfigurationError(`Cannot save invalid configuration: ${validation.errors.join(', ')}`); } if (this.options.backupConfig && fs.existsSync(this.configFile)) { await this.createBackup('auto-save'); } this.config = configToSave; const configJson = JSON.stringify(configToSave, null, 2); await fs.promises.writeFile(this.configFile, configJson, 'utf8'); this.lastSaveTime = Date.now(); this.emit('config:saved', configToSave); } catch (error) { if (error instanceof errors_1.ConfigurationError) { throw error; } throw new errors_1.ConfigurationError(`Failed to save configuration: ${error instanceof Error ? error.message : String(error)}`); } } async update(updates) { const newConfig = this.merge(this.config, updates); const validation = this.validate(newConfig); if (!validation.valid) { throw new errors_1.ConfigurationError(`Invalid configuration update: ${validation.errors.join(', ')}`); } const previousConfig = { ...this.config }; this.config = newConfig; if (this.options.autoSave) { if (this.saveDebounceTimer) { clearTimeout(this.saveDebounceTimer); } this.saveDebounceTimer = setTimeout(async () => { try { await this.save(); } catch (error) { this.emit('error', error); this.config = previousConfig; throw error; } }, 500); } this.emit('config:updated', newConfig, updates); } async reset(selective) { try { if (this.options.backupConfig) { await this.createBackup('reset'); } const defaultConfig = this.getDefaultConfig(); if (selective && selective.length > 0) { const newConfig = { ...this.config }; for (const key of selective) { if (key in defaultConfig) { newConfig[key] = defaultConfig[key]; } } await this.update(newConfig); } else { this.config = defaultConfig; await this.save(); } this.emit('config:reset', this.config, selective); } catch (error) { throw new errors_1.ConfigurationError(`Failed to reset configuration: ${error instanceof Error ? error.message : String(error)}`); } } getConfig() { return JSON.parse(JSON.stringify(this.config)); } validate(config) { const errors = []; const warnings = []; if (!config.version || typeof config.version !== 'string') { errors.push('Configuration version is required and must be a string'); } if (!config.theme || typeof config.theme !== 'string') { errors.push('Theme is required and must be a string'); } if (!config.layout || typeof config.layout !== 'string') { errors.push('Layout is required and must be a string'); } if (typeof config.refreshInterval !== 'number' || config.refreshInterval < 1000) { errors.push('Refresh interval must be a number >= 1000ms'); } if (!Array.isArray(config.components)) { errors.push('Components must be an array'); } else { config.components.forEach((component, index) => { if (!component.type || typeof component.type !== 'string') { errors.push(`Component ${index}: type is required and must be a string`); } if (!component.position || typeof component.position !== 'object') { errors.push(`Component ${index}: position is required and must be an object`); } if (typeof component.visible !== 'boolean') { errors.push(`Component ${index}: visible must be a boolean`); } }); } if (config.customThemes && !Array.isArray(config.customThemes)) { errors.push('Custom themes must be an array'); } if (config.customLayouts && !Array.isArray(config.customLayouts)) { errors.push('Custom layouts must be an array'); } if (config.preferences && typeof config.preferences !== 'object') { errors.push('Preferences must be an object'); } if (config.refreshInterval < 2000) { warnings.push('Refresh interval below 2000ms may impact performance'); } if (config.components && config.components.length > 10) { warnings.push('More than 10 components may impact performance'); } return { valid: errors.length === 0, errors, warnings, }; } merge(target, source) { const result = { ...target }; for (const key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { const sourceValue = source[key]; const targetValue = result[key]; if (sourceValue && typeof sourceValue === 'object' && !Array.isArray(sourceValue) && targetValue && typeof targetValue === 'object' && !Array.isArray(targetValue)) { result[key] = this.merge(targetValue, sourceValue); } else { result[key] = sourceValue; } } } return result; } async createBackup(reason) { const timestamp = new Date(); const backupFileName = `monitoring-${timestamp.toISOString().replace(/[:.]/g, '-')}.json`; const backupFilePath = path.join(this.backupDir, backupFileName); const backup = { timestamp, version: this.config.version, config: this.config, reason, }; await fs.promises.writeFile(backupFilePath, JSON.stringify(backup, null, 2), 'utf8'); await this.cleanupBackups(); this.emit('config:backup-created', backup); return backup; } async cleanupBackups() { try { const files = await fs.promises.readdir(this.backupDir); const backupFiles = files .filter(file => file.startsWith('monitoring-') && file.endsWith('.json')) .map(file => ({ name: file, path: path.join(this.backupDir, file), stat: fs.statSync(path.join(this.backupDir, file)), })) .sort((a, b) => b.stat.mtime.getTime() - a.stat.mtime.getTime()); const filesToDelete = backupFiles.slice(10); for (const file of filesToDelete) { await fs.promises.unlink(file.path); } } catch (error) { this.emit('error', new Error(`Failed to cleanup backups: ${error}`)); } } ensureDirectories() { try { if (!fs.existsSync(this.configDir)) { fs.mkdirSync(this.configDir, { recursive: true }); } if (!fs.existsSync(this.backupDir)) { fs.mkdirSync(this.backupDir, { recursive: true }); } } catch (error) { throw new errors_1.ConfigurationError(`Failed to create configuration directories: ${error}`); } } setupFileWatcher() { try { this.fileWatcher = fs.watch(this.configFile, (eventType) => { if (eventType === 'change') { const now = Date.now(); if (now - this.lastSaveTime > 1000) { this.load().catch(error => { this.emit('error', new Error(`Failed to reload config: ${error}`)); }); } } }); } catch (error) { this.emit('warning', new Error(`File watching not available: ${error}`)); } } async migrateIfNeeded() { const currentVersion = this.config.version; const latestVersion = this.getDefaultConfig().version; if (currentVersion !== latestVersion) { await this.createBackup(`migration-from-${currentVersion}-to-${latestVersion}`); this.config = this.mergeWithDefaults(this.config); this.config.version = latestVersion; await this.save(); this.emit('config:migrated', currentVersion, latestVersion); } } mergeWithDefaults(config) { return this.merge(this.getDefaultConfig(), config); } getDefaultConfig() { return { version: '1.0.0', theme: 'default', layout: 'default', refreshInterval: 5000, components: [ { type: 'stream-metrics', position: { x: 0, y: 0, width: 8, height: 6 }, size: { minWidth: 40, minHeight: 15 }, config: { showBitrate: true, showFps: true, showViewers: true, showUptime: true, refreshInterval: 5000, }, visible: true, priority: 1, }, { type: 'channel-status', position: { x: 8, y: 0, width: 4, height: 6 }, size: { minWidth: 30, minHeight: 15 }, config: { showInactive: true, maxChannels: 20, sortBy: 'activity', refreshInterval: 10000, }, visible: true, priority: 2, }, { type: 'system-resources', position: { x: 0, y: 6, width: 6, height: 6 }, size: { minWidth: 30, minHeight: 15 }, config: { showCpu: true, showMemory: true, showNetwork: true, showDisk: false, refreshInterval: 2000, }, visible: true, priority: 3, }, { type: 'alert-panel', position: { x: 6, y: 6, width: 6, height: 6 }, size: { minWidth: 30, minHeight: 15 }, config: { maxAlerts: 50, showTimestamp: true, autoScroll: true, }, visible: true, priority: 4, }, ], customThemes: [], customLayouts: [], preferences: { autoSave: true, confirmActions: true, showHelp: true, keyboardShortcuts: true, animationSpeed: 'normal', soundEnabled: false, compactMode: false, showTimestamps: true, maxHistoryItems: 100, }, }; } destroy() { if (this.fileWatcher) { this.fileWatcher.close(); } if (this.saveDebounceTimer) { clearTimeout(this.saveDebounceTimer); } this.removeAllListeners(); } } exports.ConfigManager = ConfigManager; exports.configManager = new ConfigManager(); //# sourceMappingURL=config-manager.js.map