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