UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

275 lines 10.7 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.SceneConfigLoader = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const os = __importStar(require("os")); const yaml = __importStar(require("js-yaml")); const js_yaml_1 = require("js-yaml"); const SUPPORTED_RESOURCE_TYPES = ['channel', 'product', 'coupon', 'watchCondition', 'productEnabled', 'couponEnabled', 'couponChannel']; class SceneConfigLoader { constructor() { this.builtinScenesPath = path.join(__dirname, '..', 'setup-scenes'); this.userScenesPath = path.join(os.homedir(), '.polyv', 'scenes'); } async loadScene(name) { try { return await this.loadBuiltinScene(name); } catch (builtinError) { try { return await this.loadUserScene(name); } catch (userError) { throw new Error(`Scene "${name}" not found. Use 'setup --list' to see available scenes.`); } } } async loadBuiltinScene(name) { const scenePath = path.join(this.builtinScenesPath, `${name}.yaml`); if (!fs.existsSync(scenePath)) { throw new Error(`Builtin scene "${name}" not found`); } const content = fs.readFileSync(scenePath, 'utf-8'); let config; try { config = yaml.load(content); } catch (e) { if (e instanceof js_yaml_1.YAMLException) { throw new Error(`Invalid YAML in builtin scene "${name}": ${e.message}`); } throw e; } const errors = this.validateConfig(config); if (errors.length > 0) { throw new Error(`Invalid scene config: ${errors.join(', ')}`); } return config; } async loadUserScene(name) { const scenePath = path.join(this.userScenesPath, `${name}.yaml`); if (!fs.existsSync(scenePath)) { throw new Error(`User scene "${name}" not found`); } const content = fs.readFileSync(scenePath, 'utf-8'); let config; try { config = yaml.load(content); } catch (e) { if (e instanceof js_yaml_1.YAMLException) { throw new Error(`Invalid YAML in user scene "${name}": ${e.message}`); } throw e; } const errors = this.validateConfig(config); if (errors.length > 0) { throw new Error(`Invalid scene config: ${errors.join(', ')}`); } return config; } listBuiltinScenes() { if (!fs.existsSync(this.builtinScenesPath)) { return []; } const files = fs.readdirSync(this.builtinScenesPath); return files .filter(file => file.endsWith('.yaml') || file.endsWith('.yml')) .map(file => path.basename(file, path.extname(file))); } listUserScenes() { if (!fs.existsSync(this.userScenesPath)) { return []; } const files = fs.readdirSync(this.userScenesPath); return files .filter(file => file.endsWith('.yaml') || file.endsWith('.yml')) .map(file => path.basename(file, path.extname(file))); } listAllScenes() { return { builtin: this.listBuiltinScenes(), user: this.listUserScenes(), }; } getSceneInfo(name) { try { const config = this.loadSceneSync(name); const info = { name: config.name, resources: config.resources.length, }; if (config.description) info.description = config.description; if (config.metadata?.icon) info.icon = config.metadata.icon; if (config.metadata?.category) info.category = config.metadata.category; if (config.metadata?.tags) info.tags = config.metadata.tags; return info; } catch { return { name }; } } loadSceneSync(name) { let scenePath = path.join(this.builtinScenesPath, `${name}.yaml`); if (!fs.existsSync(scenePath)) { scenePath = path.join(this.userScenesPath, `${name}.yaml`); } if (!fs.existsSync(scenePath)) { throw new Error(`Scene "${name}" not found`); } const content = fs.readFileSync(scenePath, 'utf-8'); return yaml.load(content); } getExecutionOrder(config) { const resources = config.resources; const ordered = []; const visited = new Set(); const visiting = new Set(); const visit = (resource) => { if (visited.has(resource.id)) { return; } if (visiting.has(resource.id)) { throw new Error(`Circular dependency detected at resource "${resource.id}"`); } visiting.add(resource.id); if (resource.dependsOn) { const deps = Array.isArray(resource.dependsOn) ? resource.dependsOn : [resource.dependsOn]; for (const depId of deps) { const depResource = resources.find(r => r.id === depId); if (!depResource) { throw new Error(`Dependency "${depId}" not found for resource "${resource.id}"`); } visit(depResource); } } visiting.delete(resource.id); visited.add(resource.id); ordered.push(resource.id); }; for (const resource of resources) { visit(resource); } return ordered; } validateConfig(config) { const errors = []; if (!config.name || config.name.trim() === '') { errors.push('name is required'); } if (!config.version || config.version.trim() === '') { errors.push('version is required'); } if (!Array.isArray(config.resources) || config.resources.length === 0) { errors.push('resources must not be empty'); return errors; } const resourceIds = config.resources.map((r) => r.id).filter((id) => typeof id === 'string'); const uniqueIds = new Set(resourceIds); if (resourceIds.length !== uniqueIds.size) { const duplicates = resourceIds.filter((id, index) => resourceIds.indexOf(id) !== index); errors.push(`duplicate resource IDs detected: ${[...new Set(duplicates)].join(', ')}`); } const resourceIdSet = uniqueIds; for (const resource of config.resources) { if (!resource.id || resource.id.trim() === '') { errors.push('resource id is required'); } if (!resource.type) { errors.push(`resource type is required for "${resource.id || 'unknown'}"`); } else if (!SUPPORTED_RESOURCE_TYPES.includes(resource.type)) { errors.push(`invalid resource type: ${resource.type}`); } if (resource.dependsOn) { const deps = Array.isArray(resource.dependsOn) ? resource.dependsOn : [resource.dependsOn]; for (const dep of deps) { if (!resourceIdSet.has(dep)) { errors.push(`dependsOn references nonexistent resource "${dep}" in "${resource.id}"`); } } } if (resource.params && typeof resource.params === 'object') { this.validateOutputFieldRefs(resource.params, resourceIdSet, resource.id, errors); } } try { this.getExecutionOrder(config); } catch (error) { if (error instanceof Error && error.message.includes('Circular dependency')) { errors.push('circular dependency detected'); } } return errors; } validateOutputFieldRefs(obj, resourceIds, resourceId, errors) { if (typeof obj === 'string') { const refPattern = /\{([a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9_]*)\}/g; let match; while ((match = refPattern.exec(obj)) !== null) { const refPath = match[1]; if (refPath) { const [refResourceId] = refPath.split('.'); if (refResourceId && !resourceIds.has(refResourceId)) { errors.push(`reference to undefined resource "${refResourceId}" in "${resourceId}"`); } } } } else if (Array.isArray(obj)) { for (const item of obj) { this.validateOutputFieldRefs(item, resourceIds, resourceId, errors); } } else if (typeof obj === 'object' && obj !== null) { for (const value of Object.values(obj)) { this.validateOutputFieldRefs(value, resourceIds, resourceId, errors); } } } } exports.SceneConfigLoader = SceneConfigLoader; //# sourceMappingURL=scene-config-loader.js.map