polyv-live-cli
Version:
CLI tool for managing PolyV live streaming services.
404 lines • 16 kB
JavaScript
"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-live-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