import { defineCommand } from "citty"; import type { Example } from "./_examples.js"; import { existsSync, readFileSync } from "node:fs"; import { resolve, dirname } from "node:path"; export const examples: Example[] = [ ["List compositions in the current project", "hyperframes compositions"], ["Output as JSON", "hyperframes compositions --json"], ]; import { c } from "../ui/colors.js"; import { ensureDOMParser } from "../utils/dom.js"; import { resolveProject } from "../utils/project.js"; import { withMeta } from "../utils/updateCheck.js"; interface CompositionInfo { id: string; duration: number; width: number; height: number; elementCount: number; source?: string; } function parseCompositions(html: string, baseDir: string): CompositionInfo[] { const parser = new DOMParser(); const doc = parser.parseFromString(html, "text/html"); const compositionDivs = doc.querySelectorAll("[data-composition-id]"); const compositions: CompositionInfo[] = []; compositionDivs.forEach((div) => { const id = div.getAttribute("data-composition-id") ?? "unknown"; const width = parseInt(div.getAttribute("data-width") ?? "1920", 10); const height = parseInt(div.getAttribute("data-height") ?? "1080", 10); const compositionSrc = div.getAttribute("data-composition-src"); // If this references an external sub-composition, parse that file if (compositionSrc) { const subPath = resolve(baseDir, compositionSrc); if (existsSync(subPath)) { const subHtml = readFileSync(subPath, "utf-8"); const subInfo = parseSubComposition(subHtml, id, width, height); compositions.push({ ...subInfo, source: compositionSrc }); return; } } const timedChildren = div.querySelectorAll("[data-start]"); let maxEnd = 0; let elementCount = 0; timedChildren.forEach((el) => { elementCount++; const start = parseFloat(el.getAttribute("data-start") ?? "0"); const endAttr = el.getAttribute("data-end"); const durationAttr = el.getAttribute("data-duration"); let end: number; if (endAttr) { end = parseFloat(endAttr); } else if (durationAttr) { end = start + parseFloat(durationAttr); } else { end = start + 5; } if (end > maxEnd) { maxEnd = end; } }); compositions.push({ id, duration: maxEnd, width, height, elementCount, }); }); return compositions; } function parseSubComposition( html: string, fallbackId: string, fallbackWidth: number, fallbackHeight: number, ): CompositionInfo { const parser = new DOMParser(); const doc = parser.parseFromString(html, "text/html"); // Sub-compositions may use