hyperframes 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +738 -262
- package/dist/hyperframe-runtime.js +5 -5
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +5 -5
- package/dist/skills/hyperframes/SKILL.md +76 -5
- package/dist/skills/hyperframes/house-style.md +1 -1
- package/dist/skills/hyperframes/references/captions.md +1 -1
- package/dist/skills/hyperframes/references/examples.md +1 -1
- package/dist/skills/hyperframes/references/fonts.md +134 -0
- package/dist/skills/hyperframes/references/motion-principles.md +69 -0
- package/dist/skills/hyperframes/references/transitions/catalog.md +2 -2
- package/dist/studio/assets/index-BkSXiHxK.js +290 -0
- package/dist/studio/assets/index-DHr9yo58.css +1 -0
- package/dist/studio/index.html +2 -2
- package/package.json +1 -1
- package/dist/studio/assets/index-B0VCLOXQ.js +0 -93
- package/dist/studio/assets/index-BT9D8I7B.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -54,7 +54,7 @@ var VERSION;
|
|
|
54
54
|
var init_version = __esm({
|
|
55
55
|
"src/version.ts"() {
|
|
56
56
|
"use strict";
|
|
57
|
-
VERSION = true ? "0.2.
|
|
57
|
+
VERSION = true ? "0.2.5" : "0.0.0-dev";
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
60
|
|
|
@@ -2731,7 +2731,7 @@ import { get as httpsGet } from "https";
|
|
|
2731
2731
|
import { pipeline } from "stream/promises";
|
|
2732
2732
|
function downloadFile(url, dest) {
|
|
2733
2733
|
const tmp = `${dest}.tmp`;
|
|
2734
|
-
return new Promise((
|
|
2734
|
+
return new Promise((resolve28, reject) => {
|
|
2735
2735
|
const follow = (u) => {
|
|
2736
2736
|
httpsGet(u, (res) => {
|
|
2737
2737
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
@@ -2748,7 +2748,7 @@ function downloadFile(url, dest) {
|
|
|
2748
2748
|
const file = createWriteStream(tmp);
|
|
2749
2749
|
pipeline(res, file).then(() => {
|
|
2750
2750
|
renameSync(tmp, dest);
|
|
2751
|
-
|
|
2751
|
+
resolve28();
|
|
2752
2752
|
}).catch((err) => {
|
|
2753
2753
|
try {
|
|
2754
2754
|
unlinkSync(tmp);
|
|
@@ -3401,13 +3401,13 @@ function hasNpx() {
|
|
|
3401
3401
|
}
|
|
3402
3402
|
}
|
|
3403
3403
|
function runSkillsAdd(repo) {
|
|
3404
|
-
return new Promise((
|
|
3404
|
+
return new Promise((resolve28, reject) => {
|
|
3405
3405
|
const child = spawn("npx", ["skills", "add", repo, "--all"], {
|
|
3406
3406
|
stdio: "inherit",
|
|
3407
3407
|
timeout: 12e4
|
|
3408
3408
|
});
|
|
3409
3409
|
child.on("close", (code, signal) => {
|
|
3410
|
-
if (code === 0)
|
|
3410
|
+
if (code === 0) resolve28();
|
|
3411
3411
|
else if (signal === "SIGINT" || code === 130) process.exit(0);
|
|
3412
3412
|
else reject(new Error(`npx skills add exited with code ${code}`));
|
|
3413
3413
|
});
|
|
@@ -3554,7 +3554,8 @@ var init_utils = __esm({
|
|
|
3554
3554
|
|
|
3555
3555
|
// ../core/src/lint/context.ts
|
|
3556
3556
|
function buildLintContext(html, options = {}) {
|
|
3557
|
-
|
|
3557
|
+
const rawSource = html || "";
|
|
3558
|
+
let source = rawSource;
|
|
3558
3559
|
const templateMatch = source.match(/<template[^>]*>([\s\S]*)<\/template>/i);
|
|
3559
3560
|
if (templateMatch?.[1]) source = templateMatch[1];
|
|
3560
3561
|
const tags = extractOpenTags(source);
|
|
@@ -3565,6 +3566,7 @@ function buildLintContext(html, options = {}) {
|
|
|
3565
3566
|
const rootCompositionId = readAttr(rootTag?.raw || "", "data-composition-id");
|
|
3566
3567
|
return {
|
|
3567
3568
|
source,
|
|
3569
|
+
rawSource,
|
|
3568
3570
|
tags,
|
|
3569
3571
|
styles,
|
|
3570
3572
|
scripts,
|
|
@@ -4575,8 +4577,30 @@ ${right.raw}`)
|
|
|
4575
4577
|
findings.push({
|
|
4576
4578
|
code: "gsap_infinite_repeat",
|
|
4577
4579
|
severity: "error",
|
|
4578
|
-
message: "GSAP tween uses `repeat: -1` (infinite). Infinite repeats break the deterministic capture engine which seeks to exact frame times. Use a finite repeat count calculated from the composition duration: `repeat: Math.
|
|
4579
|
-
fixHint: "Replace `repeat: -1` with a finite count, e.g. `repeat: Math.
|
|
4580
|
+
message: "GSAP tween uses `repeat: -1` (infinite). Infinite repeats break the deterministic capture engine which seeks to exact frame times. Use a finite repeat count calculated from the composition duration: `repeat: Math.floor(duration / cycleDuration) - 1`.",
|
|
4581
|
+
fixHint: "Replace `repeat: -1` with a finite count, e.g. `repeat: Math.floor(totalDuration / singleCycleDuration) - 1`. Use Math.floor (not Math.ceil) to ensure the animation fits within the total duration.",
|
|
4582
|
+
snippet: truncateSnippet(snippet)
|
|
4583
|
+
});
|
|
4584
|
+
}
|
|
4585
|
+
}
|
|
4586
|
+
return findings;
|
|
4587
|
+
},
|
|
4588
|
+
// gsap_repeat_ceil_overshoot
|
|
4589
|
+
({ scripts }) => {
|
|
4590
|
+
const findings = [];
|
|
4591
|
+
for (const script of scripts) {
|
|
4592
|
+
const content = script.content;
|
|
4593
|
+
const pattern = /repeat\s*:\s*Math\.ceil\s*\([^)]+\)\s*-\s*1/g;
|
|
4594
|
+
let match;
|
|
4595
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
4596
|
+
const contextStart = Math.max(0, match.index - 40);
|
|
4597
|
+
const contextEnd = Math.min(content.length, match.index + match[0].length + 40);
|
|
4598
|
+
const snippet = content.slice(contextStart, contextEnd).trim();
|
|
4599
|
+
findings.push({
|
|
4600
|
+
code: "gsap_repeat_ceil_overshoot",
|
|
4601
|
+
severity: "warning",
|
|
4602
|
+
message: "GSAP repeat calculation uses `Math.ceil` which can overshoot the composition duration. For example, Math.ceil(10.5 / 2) - 1 = 5 repeats \u2192 6 cycles \xD7 2s = 12s, exceeding 10.5s.",
|
|
4603
|
+
fixHint: "Use `Math.floor` instead of `Math.ceil` to ensure the animation fits within the duration: `repeat: Math.floor(totalDuration / cycleDuration) - 1`. Math.floor(10.5 / 2) - 1 = 4 repeats \u2192 5 cycles \xD7 2s = 10s \u2713",
|
|
4580
4604
|
snippet: truncateSnippet(snippet)
|
|
4581
4605
|
});
|
|
4582
4606
|
}
|
|
@@ -4971,6 +4995,76 @@ var init_composition = __esm({
|
|
|
4971
4995
|
}
|
|
4972
4996
|
return findings;
|
|
4973
4997
|
},
|
|
4998
|
+
// root_composition_missing_data_start
|
|
4999
|
+
({ rootTag }) => {
|
|
5000
|
+
const findings = [];
|
|
5001
|
+
if (!rootTag) return findings;
|
|
5002
|
+
const compId = readAttr(rootTag.raw, "data-composition-id");
|
|
5003
|
+
if (!compId) return findings;
|
|
5004
|
+
const hasStart = readAttr(rootTag.raw, "data-start") !== null;
|
|
5005
|
+
if (!hasStart) {
|
|
5006
|
+
findings.push({
|
|
5007
|
+
code: "root_composition_missing_data_start",
|
|
5008
|
+
severity: "warning",
|
|
5009
|
+
message: `Root composition "${compId}" is missing data-start. The runtime needs data-start="0" on the root element to begin playback.`,
|
|
5010
|
+
fixHint: 'Add data-start="0" to the root composition element.',
|
|
5011
|
+
snippet: truncateSnippet(rootTag.raw)
|
|
5012
|
+
});
|
|
5013
|
+
}
|
|
5014
|
+
return findings;
|
|
5015
|
+
},
|
|
5016
|
+
// root_composition_missing_data_duration
|
|
5017
|
+
({ rootTag }) => {
|
|
5018
|
+
const findings = [];
|
|
5019
|
+
if (!rootTag) return findings;
|
|
5020
|
+
const compId = readAttr(rootTag.raw, "data-composition-id");
|
|
5021
|
+
if (!compId) return findings;
|
|
5022
|
+
const hasDuration = readAttr(rootTag.raw, "data-duration") !== null;
|
|
5023
|
+
if (!hasDuration) {
|
|
5024
|
+
findings.push({
|
|
5025
|
+
code: "root_composition_missing_data_duration",
|
|
5026
|
+
severity: "warning",
|
|
5027
|
+
message: `Root composition "${compId}" is missing data-duration. Without an explicit duration, the runtime may infer Infinity for compositions with repeating animations, causing playback issues.`,
|
|
5028
|
+
fixHint: 'Add data-duration="X" to the root composition element, where X is the total duration in seconds.',
|
|
5029
|
+
snippet: truncateSnippet(rootTag.raw)
|
|
5030
|
+
});
|
|
5031
|
+
}
|
|
5032
|
+
return findings;
|
|
5033
|
+
},
|
|
5034
|
+
// standalone_composition_wrapped_in_template
|
|
5035
|
+
({ rawSource, options }) => {
|
|
5036
|
+
const findings = [];
|
|
5037
|
+
if (options.isSubComposition) return findings;
|
|
5038
|
+
const trimmed = rawSource.trimStart().toLowerCase();
|
|
5039
|
+
if (trimmed.startsWith("<template")) {
|
|
5040
|
+
findings.push({
|
|
5041
|
+
code: "standalone_composition_wrapped_in_template",
|
|
5042
|
+
severity: "warning",
|
|
5043
|
+
message: "Root index.html is wrapped in a <template> tag. Only sub-compositions loaded via data-composition-src should use <template> wrappers. The runtime cannot play a standalone composition inside a template.",
|
|
5044
|
+
fixHint: "Remove the <template> wrapper. Use <!DOCTYPE html><html>...<div data-composition-id>...</div>...</html> instead."
|
|
5045
|
+
});
|
|
5046
|
+
}
|
|
5047
|
+
return findings;
|
|
5048
|
+
},
|
|
5049
|
+
// root_composition_missing_html_wrapper
|
|
5050
|
+
({ rawSource, rootTag, options }) => {
|
|
5051
|
+
const findings = [];
|
|
5052
|
+
if (options.isSubComposition) return findings;
|
|
5053
|
+
const trimmed = rawSource.trimStart().toLowerCase();
|
|
5054
|
+
if (trimmed.startsWith("<template")) return findings;
|
|
5055
|
+
const hasDoctype = trimmed.startsWith("<!doctype") || trimmed.startsWith("<html");
|
|
5056
|
+
const hasComposition = rawSource.includes("data-composition-id");
|
|
5057
|
+
if (hasComposition && !hasDoctype) {
|
|
5058
|
+
findings.push({
|
|
5059
|
+
code: "root_composition_missing_html_wrapper",
|
|
5060
|
+
severity: "error",
|
|
5061
|
+
message: "Composition starts with a bare element instead of a proper HTML document. An index.html that contains data-composition-id but no <!DOCTYPE html>, <html>, or <body> is a fragment \u2014 browsers quirks-mode it, the preview server cannot load it, and the bundler will fail to inject runtime scripts.",
|
|
5062
|
+
fixHint: 'Wrap the composition in <!DOCTYPE html><html><head><meta charset="UTF-8"></head><body>...</body></html>.',
|
|
5063
|
+
snippet: rootTag ? truncateSnippet(rootTag.raw) : void 0
|
|
5064
|
+
});
|
|
5065
|
+
}
|
|
5066
|
+
return findings;
|
|
5067
|
+
},
|
|
4974
5068
|
// requestanimationframe_in_composition
|
|
4975
5069
|
({ scripts }) => {
|
|
4976
5070
|
const findings = [];
|
|
@@ -5192,7 +5286,7 @@ function lintProject(project) {
|
|
|
5192
5286
|
const filePath = join6(compositionsDir, file);
|
|
5193
5287
|
const html = readFileSync6(filePath, "utf-8");
|
|
5194
5288
|
allHtmlSources.push(html);
|
|
5195
|
-
const result = lintHyperframeHtml(html, { filePath });
|
|
5289
|
+
const result = lintHyperframeHtml(html, { filePath, isSubComposition: true });
|
|
5196
5290
|
results.push({ file: `compositions/${file}`, result });
|
|
5197
5291
|
totalErrors += result.errorCount;
|
|
5198
5292
|
totalWarnings += result.warningCount;
|
|
@@ -5328,6 +5422,238 @@ var init_lintFormat = __esm({
|
|
|
5328
5422
|
}
|
|
5329
5423
|
});
|
|
5330
5424
|
|
|
5425
|
+
// src/server/portUtils.ts
|
|
5426
|
+
import net from "net";
|
|
5427
|
+
import http from "http";
|
|
5428
|
+
import { execFile } from "child_process";
|
|
5429
|
+
import { promisify } from "util";
|
|
5430
|
+
import { resolve as resolve2 } from "path";
|
|
5431
|
+
function isPortAvailableOnHost(port, host) {
|
|
5432
|
+
return new Promise((resolve28) => {
|
|
5433
|
+
const server = net.createServer();
|
|
5434
|
+
server.unref();
|
|
5435
|
+
server.on("error", (err) => {
|
|
5436
|
+
resolve28(err.code !== "EADDRINUSE");
|
|
5437
|
+
});
|
|
5438
|
+
server.listen({ port, host }, () => {
|
|
5439
|
+
server.close(() => {
|
|
5440
|
+
resolve28(true);
|
|
5441
|
+
});
|
|
5442
|
+
});
|
|
5443
|
+
});
|
|
5444
|
+
}
|
|
5445
|
+
async function testPortOnAllHosts(port) {
|
|
5446
|
+
const hosts = ["127.0.0.1", "0.0.0.0", "::1", "::"];
|
|
5447
|
+
const results = await Promise.all(hosts.map((h2) => isPortAvailableOnHost(port, h2)));
|
|
5448
|
+
return results.every(Boolean);
|
|
5449
|
+
}
|
|
5450
|
+
function detectHyperframesServer(port, normalizedProjectDir) {
|
|
5451
|
+
return new Promise((resolveResult) => {
|
|
5452
|
+
const req = http.get(
|
|
5453
|
+
{
|
|
5454
|
+
hostname: "127.0.0.1",
|
|
5455
|
+
port,
|
|
5456
|
+
path: "/__hyperframes_config",
|
|
5457
|
+
timeout: PROBE_TIMEOUT_MS
|
|
5458
|
+
},
|
|
5459
|
+
(res) => {
|
|
5460
|
+
if (res.statusCode !== 200) {
|
|
5461
|
+
res.resume();
|
|
5462
|
+
return resolveResult({ type: "not-hyperframes" });
|
|
5463
|
+
}
|
|
5464
|
+
let data = "";
|
|
5465
|
+
let bytes = 0;
|
|
5466
|
+
res.on("data", (chunk) => {
|
|
5467
|
+
bytes += typeof chunk === "string" ? chunk.length : chunk.byteLength;
|
|
5468
|
+
if (bytes > PROBE_MAX_BYTES) {
|
|
5469
|
+
req.destroy();
|
|
5470
|
+
return resolveResult({ type: "not-hyperframes" });
|
|
5471
|
+
}
|
|
5472
|
+
data += chunk;
|
|
5473
|
+
});
|
|
5474
|
+
res.on("error", () => {
|
|
5475
|
+
resolveResult({ type: "not-hyperframes" });
|
|
5476
|
+
});
|
|
5477
|
+
res.on("end", () => {
|
|
5478
|
+
try {
|
|
5479
|
+
const json = JSON.parse(data);
|
|
5480
|
+
if (json.isHyperframes !== true) {
|
|
5481
|
+
return resolveResult({ type: "not-hyperframes" });
|
|
5482
|
+
}
|
|
5483
|
+
const normalize = (p) => resolve2(p).replace(/\\/g, "/").toLowerCase();
|
|
5484
|
+
if (normalize(json.projectDir) === normalizedProjectDir) {
|
|
5485
|
+
return resolveResult({ type: "match" });
|
|
5486
|
+
}
|
|
5487
|
+
return resolveResult({ type: "mismatch", projectName: json.projectName });
|
|
5488
|
+
} catch {
|
|
5489
|
+
resolveResult({ type: "not-hyperframes" });
|
|
5490
|
+
}
|
|
5491
|
+
});
|
|
5492
|
+
}
|
|
5493
|
+
);
|
|
5494
|
+
req.on("error", () => {
|
|
5495
|
+
resolveResult({ type: "not-hyperframes" });
|
|
5496
|
+
});
|
|
5497
|
+
req.on("timeout", () => {
|
|
5498
|
+
req.destroy();
|
|
5499
|
+
resolveResult({ type: "not-hyperframes" });
|
|
5500
|
+
});
|
|
5501
|
+
});
|
|
5502
|
+
}
|
|
5503
|
+
async function getProcessOnPort(port) {
|
|
5504
|
+
if (process.platform === "win32") return null;
|
|
5505
|
+
try {
|
|
5506
|
+
const { stdout: stdout2 } = await execFileAsync("lsof", [`-ti:${port}`, "-sTCP:LISTEN"], {
|
|
5507
|
+
timeout: 2e3
|
|
5508
|
+
});
|
|
5509
|
+
const pid = stdout2.trim().split("\n")[0]?.trim();
|
|
5510
|
+
return pid || null;
|
|
5511
|
+
} catch {
|
|
5512
|
+
return null;
|
|
5513
|
+
}
|
|
5514
|
+
}
|
|
5515
|
+
function probePort(port) {
|
|
5516
|
+
return new Promise((resolveResult) => {
|
|
5517
|
+
const req = http.get(
|
|
5518
|
+
{ hostname: "127.0.0.1", port, path: "/__hyperframes_config", timeout: PROBE_TIMEOUT_MS },
|
|
5519
|
+
(res) => {
|
|
5520
|
+
if (res.statusCode !== 200) {
|
|
5521
|
+
res.resume();
|
|
5522
|
+
return resolveResult(null);
|
|
5523
|
+
}
|
|
5524
|
+
let data = "";
|
|
5525
|
+
let bytes = 0;
|
|
5526
|
+
res.on("data", (chunk) => {
|
|
5527
|
+
bytes += typeof chunk === "string" ? chunk.length : chunk.byteLength;
|
|
5528
|
+
if (bytes > PROBE_MAX_BYTES) {
|
|
5529
|
+
req.destroy();
|
|
5530
|
+
return resolveResult(null);
|
|
5531
|
+
}
|
|
5532
|
+
data += chunk;
|
|
5533
|
+
});
|
|
5534
|
+
res.on("error", () => resolveResult(null));
|
|
5535
|
+
res.on("end", () => {
|
|
5536
|
+
try {
|
|
5537
|
+
const json = JSON.parse(data);
|
|
5538
|
+
resolveResult(json.isHyperframes === true ? json : null);
|
|
5539
|
+
} catch {
|
|
5540
|
+
resolveResult(null);
|
|
5541
|
+
}
|
|
5542
|
+
});
|
|
5543
|
+
}
|
|
5544
|
+
);
|
|
5545
|
+
req.on("error", () => resolveResult(null));
|
|
5546
|
+
req.on("timeout", () => {
|
|
5547
|
+
req.destroy();
|
|
5548
|
+
resolveResult(null);
|
|
5549
|
+
});
|
|
5550
|
+
});
|
|
5551
|
+
}
|
|
5552
|
+
async function scanActiveServers(startPort = 3002) {
|
|
5553
|
+
const endPort = startPort + MAX_PORT_SCAN - 1;
|
|
5554
|
+
const servers = [];
|
|
5555
|
+
const batchSize = 20;
|
|
5556
|
+
for (let batchStart = startPort; batchStart <= endPort; batchStart += batchSize) {
|
|
5557
|
+
const batchEnd = Math.min(batchStart + batchSize - 1, endPort);
|
|
5558
|
+
const ports = Array.from({ length: batchEnd - batchStart + 1 }, (_2, i) => batchStart + i);
|
|
5559
|
+
const results = await Promise.all(
|
|
5560
|
+
ports.map(async (port) => {
|
|
5561
|
+
const config = await probePort(port);
|
|
5562
|
+
if (!config) return null;
|
|
5563
|
+
const pid = await getProcessOnPort(port);
|
|
5564
|
+
return {
|
|
5565
|
+
port,
|
|
5566
|
+
projectName: config.projectName,
|
|
5567
|
+
projectDir: config.projectDir,
|
|
5568
|
+
version: config.version,
|
|
5569
|
+
pid
|
|
5570
|
+
};
|
|
5571
|
+
})
|
|
5572
|
+
);
|
|
5573
|
+
for (const r of results) {
|
|
5574
|
+
if (r) servers.push(r);
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
return servers;
|
|
5578
|
+
}
|
|
5579
|
+
async function killActiveServers(startPort = 3002) {
|
|
5580
|
+
const servers = await scanActiveServers(startPort);
|
|
5581
|
+
let killed = 0;
|
|
5582
|
+
for (const server of servers) {
|
|
5583
|
+
if (server.pid) {
|
|
5584
|
+
try {
|
|
5585
|
+
process.kill(parseInt(server.pid, 10), "SIGTERM");
|
|
5586
|
+
killed++;
|
|
5587
|
+
} catch {
|
|
5588
|
+
}
|
|
5589
|
+
}
|
|
5590
|
+
}
|
|
5591
|
+
return killed;
|
|
5592
|
+
}
|
|
5593
|
+
async function findPortAndServe(fetch3, startPort, projectDir, forceNew) {
|
|
5594
|
+
const { createAdaptorServer } = await import("@hono/node-server");
|
|
5595
|
+
const normalizedDir = resolve2(projectDir).replace(/\\/g, "/").toLowerCase();
|
|
5596
|
+
const endPort = startPort + MAX_PORT_SCAN - 1;
|
|
5597
|
+
let server = null;
|
|
5598
|
+
for (let port = startPort; port <= endPort; port++) {
|
|
5599
|
+
const available = await testPortOnAllHosts(port);
|
|
5600
|
+
if (available) {
|
|
5601
|
+
if (!server) server = createAdaptorServer({ fetch: fetch3 });
|
|
5602
|
+
try {
|
|
5603
|
+
await new Promise((resolveListener, rejectListener) => {
|
|
5604
|
+
const onError = (err) => {
|
|
5605
|
+
server.removeListener("listening", onListening);
|
|
5606
|
+
rejectListener(err);
|
|
5607
|
+
};
|
|
5608
|
+
const onListening = () => {
|
|
5609
|
+
server.removeListener("error", onError);
|
|
5610
|
+
resolveListener();
|
|
5611
|
+
};
|
|
5612
|
+
server.once("error", onError);
|
|
5613
|
+
server.once("listening", onListening);
|
|
5614
|
+
server.listen(port);
|
|
5615
|
+
});
|
|
5616
|
+
return { type: "started", server, port };
|
|
5617
|
+
} catch (err) {
|
|
5618
|
+
if (err.code === "EADDRINUSE") {
|
|
5619
|
+
continue;
|
|
5620
|
+
}
|
|
5621
|
+
throw err;
|
|
5622
|
+
}
|
|
5623
|
+
}
|
|
5624
|
+
if (!forceNew) {
|
|
5625
|
+
const detection = await detectHyperframesServer(port, normalizedDir);
|
|
5626
|
+
if (detection.type === "match") {
|
|
5627
|
+
return { type: "already-running", port };
|
|
5628
|
+
}
|
|
5629
|
+
if (detection.type === "mismatch") {
|
|
5630
|
+
console.log(
|
|
5631
|
+
` ${c.dim(`Port ${port} in use by HyperFrames project "${detection.projectName}" \u2014 skipping`)}`
|
|
5632
|
+
);
|
|
5633
|
+
continue;
|
|
5634
|
+
}
|
|
5635
|
+
}
|
|
5636
|
+
const pid = await getProcessOnPort(port);
|
|
5637
|
+
if (pid) {
|
|
5638
|
+
console.log(` ${c.dim(`Port ${port} in use by PID ${pid} \u2014 skipping`)}`);
|
|
5639
|
+
}
|
|
5640
|
+
}
|
|
5641
|
+
throw new Error(
|
|
5642
|
+
`Ports ${startPort}\u2013${endPort} are all in use. Use --port to specify a different starting port.`
|
|
5643
|
+
);
|
|
5644
|
+
}
|
|
5645
|
+
var execFileAsync, MAX_PORT_SCAN, PROBE_TIMEOUT_MS, PROBE_MAX_BYTES;
|
|
5646
|
+
var init_portUtils = __esm({
|
|
5647
|
+
"src/server/portUtils.ts"() {
|
|
5648
|
+
"use strict";
|
|
5649
|
+
init_colors();
|
|
5650
|
+
execFileAsync = promisify(execFile);
|
|
5651
|
+
MAX_PORT_SCAN = 100;
|
|
5652
|
+
PROBE_TIMEOUT_MS = 300;
|
|
5653
|
+
PROBE_MAX_BYTES = 4096;
|
|
5654
|
+
}
|
|
5655
|
+
});
|
|
5656
|
+
|
|
5331
5657
|
// src/server/fileWatcher.ts
|
|
5332
5658
|
import { watch } from "fs";
|
|
5333
5659
|
function createProjectWatcher(projectDir) {
|
|
@@ -5372,11 +5698,11 @@ var init_fileWatcher = __esm({
|
|
|
5372
5698
|
});
|
|
5373
5699
|
|
|
5374
5700
|
// ../core/src/studio-api/helpers/safePath.ts
|
|
5375
|
-
import { resolve as
|
|
5701
|
+
import { resolve as resolve3, sep, join as join7 } from "path";
|
|
5376
5702
|
import { readdirSync as readdirSync3 } from "fs";
|
|
5377
5703
|
function isSafePath(base, resolved) {
|
|
5378
|
-
const norm =
|
|
5379
|
-
return resolved.startsWith(norm) || resolved ===
|
|
5704
|
+
const norm = resolve3(base) + sep;
|
|
5705
|
+
return resolved.startsWith(norm) || resolved === resolve3(base);
|
|
5380
5706
|
}
|
|
5381
5707
|
function walkDir(dir, prefix = "") {
|
|
5382
5708
|
const files = [];
|
|
@@ -5441,7 +5767,7 @@ import {
|
|
|
5441
5767
|
renameSync as renameSync2,
|
|
5442
5768
|
readdirSync as readdirSync4
|
|
5443
5769
|
} from "fs";
|
|
5444
|
-
import { resolve as
|
|
5770
|
+
import { resolve as resolve4, dirname, join as join8 } from "path";
|
|
5445
5771
|
async function resolveProjectFile(c2, adapter2, opts) {
|
|
5446
5772
|
const id = c2.req.param("id");
|
|
5447
5773
|
const project = await adapter2.resolveProject(id);
|
|
@@ -5452,7 +5778,7 @@ async function resolveProjectFile(c2, adapter2, opts) {
|
|
|
5452
5778
|
if (filePath.includes("\0")) {
|
|
5453
5779
|
return { error: c2.json({ error: "forbidden" }, 403) };
|
|
5454
5780
|
}
|
|
5455
|
-
const absPath =
|
|
5781
|
+
const absPath = resolve4(project.dir, filePath);
|
|
5456
5782
|
if (!isSafePath(project.dir, absPath)) {
|
|
5457
5783
|
return { error: c2.json({ error: "forbidden" }, 403) };
|
|
5458
5784
|
}
|
|
@@ -5472,7 +5798,7 @@ function generateCopyPath(projectDir, originalPath) {
|
|
|
5472
5798
|
const cleanBase = copyMatch ? base.slice(0, -copyMatch[0].length) : base;
|
|
5473
5799
|
let num = copyMatch ? copyMatch[1] ? parseInt(copyMatch[1]) + 1 : 2 : 1;
|
|
5474
5800
|
let candidate = num === 1 ? `${cleanBase} (copy)${ext}` : `${cleanBase} (copy ${num})${ext}`;
|
|
5475
|
-
while (existsSync7(
|
|
5801
|
+
while (existsSync7(resolve4(projectDir, candidate))) {
|
|
5476
5802
|
num++;
|
|
5477
5803
|
candidate = `${cleanBase} (copy ${num})${ext}`;
|
|
5478
5804
|
}
|
|
@@ -5553,7 +5879,7 @@ function registerFileRoutes(api, adapter2) {
|
|
|
5553
5879
|
if (!body.newPath || body.newPath.includes("\0")) {
|
|
5554
5880
|
return c2.json({ error: "newPath required" }, 400);
|
|
5555
5881
|
}
|
|
5556
|
-
const newAbs =
|
|
5882
|
+
const newAbs = resolve4(res.project.dir, body.newPath);
|
|
5557
5883
|
if (!isSafePath(res.project.dir, newAbs)) {
|
|
5558
5884
|
return c2.json({ error: "forbidden" }, 403);
|
|
5559
5885
|
}
|
|
@@ -5572,12 +5898,12 @@ function registerFileRoutes(api, adapter2) {
|
|
|
5572
5898
|
if (!body.path || body.path.includes("\0")) {
|
|
5573
5899
|
return c2.json({ error: "path required" }, 400);
|
|
5574
5900
|
}
|
|
5575
|
-
const srcAbs =
|
|
5901
|
+
const srcAbs = resolve4(project.dir, body.path);
|
|
5576
5902
|
if (!isSafePath(project.dir, srcAbs) || !existsSync7(srcAbs)) {
|
|
5577
5903
|
return c2.json({ error: "not found" }, 404);
|
|
5578
5904
|
}
|
|
5579
5905
|
const copyPath = generateCopyPath(project.dir, body.path);
|
|
5580
|
-
const destAbs =
|
|
5906
|
+
const destAbs = resolve4(project.dir, copyPath);
|
|
5581
5907
|
if (!isSafePath(project.dir, destAbs)) {
|
|
5582
5908
|
return c2.json({ error: "forbidden" }, 403);
|
|
5583
5909
|
}
|
|
@@ -5596,7 +5922,7 @@ function registerFileRoutes(api, adapter2) {
|
|
|
5596
5922
|
const project = await adapter2.resolveProject(c2.req.param("id"));
|
|
5597
5923
|
if (!project) return c2.json({ error: "not found" }, 404);
|
|
5598
5924
|
const subDir = c2.req.query("dir") ?? "";
|
|
5599
|
-
const targetDir = subDir ?
|
|
5925
|
+
const targetDir = subDir ? resolve4(project.dir, subDir) : project.dir;
|
|
5600
5926
|
if (!isSafePath(project.dir, targetDir)) return c2.json({ error: "forbidden" }, 403);
|
|
5601
5927
|
if (subDir && !existsSync7(targetDir)) mkdirSync5(targetDir, { recursive: true });
|
|
5602
5928
|
const formData = await c2.req.formData();
|
|
@@ -5610,7 +5936,7 @@ function registerFileRoutes(api, adapter2) {
|
|
|
5610
5936
|
skipped.push(name);
|
|
5611
5937
|
continue;
|
|
5612
5938
|
}
|
|
5613
|
-
const destPath =
|
|
5939
|
+
const destPath = resolve4(targetDir, name);
|
|
5614
5940
|
if (!isSafePath(project.dir, destPath)) continue;
|
|
5615
5941
|
let finalPath = destPath;
|
|
5616
5942
|
let finalName = name;
|
|
@@ -5619,13 +5945,13 @@ function registerFileRoutes(api, adapter2) {
|
|
|
5619
5945
|
const ext = dotIdx > 0 ? name.slice(dotIdx) : "";
|
|
5620
5946
|
const base = dotIdx > 0 ? name.slice(0, dotIdx) : name;
|
|
5621
5947
|
let n = 2;
|
|
5622
|
-
while (n < 1e4 && existsSync7(
|
|
5948
|
+
while (n < 1e4 && existsSync7(resolve4(targetDir, `${base} (${n})${ext}`))) n++;
|
|
5623
5949
|
if (n >= 1e4) {
|
|
5624
5950
|
skipped.push(name);
|
|
5625
5951
|
continue;
|
|
5626
5952
|
}
|
|
5627
5953
|
finalName = `${base} (${n})${ext}`;
|
|
5628
|
-
finalPath =
|
|
5954
|
+
finalPath = resolve4(targetDir, finalName);
|
|
5629
5955
|
}
|
|
5630
5956
|
const buffer = Buffer.from(await value.arrayBuffer());
|
|
5631
5957
|
writeFileSync4(finalPath, buffer);
|
|
@@ -9535,8 +9861,8 @@ var init_custom_element_registry = __esm({
|
|
|
9535
9861
|
} : (element) => element.localName === localName;
|
|
9536
9862
|
registry.set(localName, { Class, check });
|
|
9537
9863
|
if (waiting.has(localName)) {
|
|
9538
|
-
for (const
|
|
9539
|
-
|
|
9864
|
+
for (const resolve28 of waiting.get(localName))
|
|
9865
|
+
resolve28(Class);
|
|
9540
9866
|
waiting.delete(localName);
|
|
9541
9867
|
}
|
|
9542
9868
|
ownerDocument.querySelectorAll(
|
|
@@ -9576,13 +9902,13 @@ var init_custom_element_registry = __esm({
|
|
|
9576
9902
|
*/
|
|
9577
9903
|
whenDefined(localName) {
|
|
9578
9904
|
const { registry, waiting } = this;
|
|
9579
|
-
return new Promise((
|
|
9905
|
+
return new Promise((resolve28) => {
|
|
9580
9906
|
if (registry.has(localName))
|
|
9581
|
-
|
|
9907
|
+
resolve28(registry.get(localName).Class);
|
|
9582
9908
|
else {
|
|
9583
9909
|
if (!waiting.has(localName))
|
|
9584
9910
|
waiting.set(localName, []);
|
|
9585
|
-
waiting.get(localName).push(
|
|
9911
|
+
waiting.get(localName).push(resolve28);
|
|
9586
9912
|
}
|
|
9587
9913
|
});
|
|
9588
9914
|
}
|
|
@@ -18283,7 +18609,7 @@ var init_esm10 = __esm({
|
|
|
18283
18609
|
});
|
|
18284
18610
|
|
|
18285
18611
|
// ../core/src/compiler/rewriteSubCompPaths.ts
|
|
18286
|
-
import { join as join9, resolve as
|
|
18612
|
+
import { join as join9, resolve as resolve5, dirname as dirname2 } from "path";
|
|
18287
18613
|
function isAbsoluteOrSpecial(val) {
|
|
18288
18614
|
return !val || val.startsWith("http://") || val.startsWith("https://") || val.startsWith("//") || val.startsWith("data:") || val.startsWith("#");
|
|
18289
18615
|
}
|
|
@@ -18296,7 +18622,7 @@ function rewriteAssetPath(compSrcPath, relativePath) {
|
|
|
18296
18622
|
const compDir = dirname2(compSrcPath);
|
|
18297
18623
|
if (!compDir || compDir === ".") return relativePath;
|
|
18298
18624
|
const resolved = join9(compDir, relativePath);
|
|
18299
|
-
const normalized =
|
|
18625
|
+
const normalized = resolve5("/", resolved).slice(1);
|
|
18300
18626
|
return normalized;
|
|
18301
18627
|
}
|
|
18302
18628
|
function rewriteAssetPaths(elements, compSrcPath, getAttr2, setAttr) {
|
|
@@ -18308,7 +18634,7 @@ function rewriteAssetPaths(elements, compSrcPath, getAttr2, setAttr) {
|
|
|
18308
18634
|
if (isAbsoluteOrSpecial(val)) continue;
|
|
18309
18635
|
if (!needsRewrite(val)) continue;
|
|
18310
18636
|
const rewritten = join9(compDir, val);
|
|
18311
|
-
const normalized =
|
|
18637
|
+
const normalized = resolve5("/", rewritten).slice(1);
|
|
18312
18638
|
if (normalized !== val) {
|
|
18313
18639
|
setAttr(el, attr, normalized);
|
|
18314
18640
|
}
|
|
@@ -18397,7 +18723,7 @@ var init_subComposition = __esm({
|
|
|
18397
18723
|
|
|
18398
18724
|
// ../core/src/studio-api/routes/preview.ts
|
|
18399
18725
|
import { existsSync as existsSync9, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
|
|
18400
|
-
import { resolve as
|
|
18726
|
+
import { resolve as resolve6 } from "path";
|
|
18401
18727
|
function registerPreviewRoutes(api, adapter2) {
|
|
18402
18728
|
api.get("/projects/:id/preview", async (c2) => {
|
|
18403
18729
|
const project = await adapter2.resolveProject(c2.req.param("id"));
|
|
@@ -18405,7 +18731,7 @@ function registerPreviewRoutes(api, adapter2) {
|
|
|
18405
18731
|
try {
|
|
18406
18732
|
let bundled = await adapter2.bundle(project.dir);
|
|
18407
18733
|
if (!bundled) {
|
|
18408
|
-
const indexPath =
|
|
18734
|
+
const indexPath = resolve6(project.dir, "index.html");
|
|
18409
18735
|
if (!existsSync9(indexPath)) return c2.text("not found", 404);
|
|
18410
18736
|
bundled = readFileSync9(indexPath, "utf-8");
|
|
18411
18737
|
}
|
|
@@ -18421,7 +18747,7 @@ ${runtimeTag}`;
|
|
|
18421
18747
|
}
|
|
18422
18748
|
return c2.html(bundled);
|
|
18423
18749
|
} catch {
|
|
18424
|
-
const file =
|
|
18750
|
+
const file = resolve6(project.dir, "index.html");
|
|
18425
18751
|
if (existsSync9(file)) return c2.html(readFileSync9(file, "utf-8"));
|
|
18426
18752
|
return c2.text("not found", 404);
|
|
18427
18753
|
}
|
|
@@ -18432,7 +18758,7 @@ ${runtimeTag}`;
|
|
|
18432
18758
|
const compPath = decodeURIComponent(
|
|
18433
18759
|
c2.req.path.replace(`/projects/${project.id}/preview/comp/`, "").split("?")[0] ?? ""
|
|
18434
18760
|
);
|
|
18435
|
-
const compFile =
|
|
18761
|
+
const compFile = resolve6(project.dir, compPath);
|
|
18436
18762
|
if (!isSafePath(project.dir, compFile) || !existsSync9(compFile) || !statSync2(compFile).isFile()) {
|
|
18437
18763
|
return c2.text("not found", 404);
|
|
18438
18764
|
}
|
|
@@ -18447,7 +18773,7 @@ ${runtimeTag}`;
|
|
|
18447
18773
|
const subPath = decodeURIComponent(
|
|
18448
18774
|
c2.req.path.replace(`/projects/${project.id}/preview/`, "").split("?")[0] ?? ""
|
|
18449
18775
|
);
|
|
18450
|
-
const file =
|
|
18776
|
+
const file = resolve6(project.dir, subPath);
|
|
18451
18777
|
if (!isSafePath(project.dir, file) || !existsSync9(file) || !statSync2(file).isFile()) {
|
|
18452
18778
|
return c2.text("not found", 404);
|
|
18453
18779
|
}
|
|
@@ -19823,7 +20149,7 @@ var init_timingCompiler = __esm({
|
|
|
19823
20149
|
|
|
19824
20150
|
// ../core/src/inline-scripts/hyperframesRuntime.engine.ts
|
|
19825
20151
|
import { buildSync } from "esbuild";
|
|
19826
|
-
import { dirname as dirname3, resolve as
|
|
20152
|
+
import { dirname as dirname3, resolve as resolve7 } from "path";
|
|
19827
20153
|
import { fileURLToPath } from "url";
|
|
19828
20154
|
var init_hyperframesRuntime_engine = __esm({
|
|
19829
20155
|
"../core/src/inline-scripts/hyperframesRuntime.engine.ts"() {
|
|
@@ -19896,17 +20222,25 @@ var init_gsap2 = __esm({
|
|
|
19896
20222
|
}
|
|
19897
20223
|
});
|
|
19898
20224
|
|
|
19899
|
-
// ../../node_modules/.bun/@chenglou+pretext@0.0.
|
|
20225
|
+
// ../../node_modules/.bun/@chenglou+pretext@0.0.5/node_modules/@chenglou/pretext/dist/generated/bidi-data.js
|
|
20226
|
+
var init_bidi_data = __esm({
|
|
20227
|
+
"../../node_modules/.bun/@chenglou+pretext@0.0.5/node_modules/@chenglou/pretext/dist/generated/bidi-data.js"() {
|
|
20228
|
+
"use strict";
|
|
20229
|
+
}
|
|
20230
|
+
});
|
|
20231
|
+
|
|
20232
|
+
// ../../node_modules/.bun/@chenglou+pretext@0.0.5/node_modules/@chenglou/pretext/dist/bidi.js
|
|
19900
20233
|
var init_bidi = __esm({
|
|
19901
|
-
"../../node_modules/.bun/@chenglou+pretext@0.0.
|
|
20234
|
+
"../../node_modules/.bun/@chenglou+pretext@0.0.5/node_modules/@chenglou/pretext/dist/bidi.js"() {
|
|
19902
20235
|
"use strict";
|
|
20236
|
+
init_bidi_data();
|
|
19903
20237
|
}
|
|
19904
20238
|
});
|
|
19905
20239
|
|
|
19906
|
-
// ../../node_modules/.bun/@chenglou+pretext@0.0.
|
|
20240
|
+
// ../../node_modules/.bun/@chenglou+pretext@0.0.5/node_modules/@chenglou/pretext/dist/analysis.js
|
|
19907
20241
|
var arabicScriptRe, combiningMarkRe, decimalDigitRe;
|
|
19908
20242
|
var init_analysis = __esm({
|
|
19909
|
-
"../../node_modules/.bun/@chenglou+pretext@0.0.
|
|
20243
|
+
"../../node_modules/.bun/@chenglou+pretext@0.0.5/node_modules/@chenglou/pretext/dist/analysis.js"() {
|
|
19910
20244
|
"use strict";
|
|
19911
20245
|
arabicScriptRe = new RegExp("\\p{Script=Arabic}", "u");
|
|
19912
20246
|
combiningMarkRe = new RegExp("\\p{M}", "u");
|
|
@@ -19914,27 +20248,27 @@ var init_analysis = __esm({
|
|
|
19914
20248
|
}
|
|
19915
20249
|
});
|
|
19916
20250
|
|
|
19917
|
-
// ../../node_modules/.bun/@chenglou+pretext@0.0.
|
|
20251
|
+
// ../../node_modules/.bun/@chenglou+pretext@0.0.5/node_modules/@chenglou/pretext/dist/measurement.js
|
|
19918
20252
|
var emojiPresentationRe;
|
|
19919
20253
|
var init_measurement = __esm({
|
|
19920
|
-
"../../node_modules/.bun/@chenglou+pretext@0.0.
|
|
20254
|
+
"../../node_modules/.bun/@chenglou+pretext@0.0.5/node_modules/@chenglou/pretext/dist/measurement.js"() {
|
|
19921
20255
|
"use strict";
|
|
19922
20256
|
init_analysis();
|
|
19923
20257
|
emojiPresentationRe = new RegExp("\\p{Emoji_Presentation}", "u");
|
|
19924
20258
|
}
|
|
19925
20259
|
});
|
|
19926
20260
|
|
|
19927
|
-
// ../../node_modules/.bun/@chenglou+pretext@0.0.
|
|
20261
|
+
// ../../node_modules/.bun/@chenglou+pretext@0.0.5/node_modules/@chenglou/pretext/dist/line-break.js
|
|
19928
20262
|
var init_line_break = __esm({
|
|
19929
|
-
"../../node_modules/.bun/@chenglou+pretext@0.0.
|
|
20263
|
+
"../../node_modules/.bun/@chenglou+pretext@0.0.5/node_modules/@chenglou/pretext/dist/line-break.js"() {
|
|
19930
20264
|
"use strict";
|
|
19931
20265
|
init_measurement();
|
|
19932
20266
|
}
|
|
19933
20267
|
});
|
|
19934
20268
|
|
|
19935
|
-
// ../../node_modules/.bun/@chenglou+pretext@0.0.
|
|
20269
|
+
// ../../node_modules/.bun/@chenglou+pretext@0.0.5/node_modules/@chenglou/pretext/dist/layout.js
|
|
19936
20270
|
var init_layout = __esm({
|
|
19937
|
-
"../../node_modules/.bun/@chenglou+pretext@0.0.
|
|
20271
|
+
"../../node_modules/.bun/@chenglou+pretext@0.0.5/node_modules/@chenglou/pretext/dist/layout.js"() {
|
|
19938
20272
|
"use strict";
|
|
19939
20273
|
init_bidi();
|
|
19940
20274
|
init_analysis();
|
|
@@ -19990,18 +20324,35 @@ async function getCdpSession(page) {
|
|
|
19990
20324
|
}
|
|
19991
20325
|
return client;
|
|
19992
20326
|
}
|
|
20327
|
+
async function sendBeginFrame(client, params) {
|
|
20328
|
+
for (let attempt = 0; ; attempt++) {
|
|
20329
|
+
try {
|
|
20330
|
+
return await client.send("HeadlessExperimental.beginFrame", params);
|
|
20331
|
+
} catch (err) {
|
|
20332
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
20333
|
+
const isPending = msg.includes("Another frame is pending");
|
|
20334
|
+
if (isPending && attempt < PENDING_FRAME_RETRIES) {
|
|
20335
|
+
await new Promise((r) => setTimeout(r, 50 * 2 ** attempt));
|
|
20336
|
+
continue;
|
|
20337
|
+
}
|
|
20338
|
+
if (isPending) {
|
|
20339
|
+
throw new Error(
|
|
20340
|
+
`[BeginFrame] Frame still pending after ${PENDING_FRAME_RETRIES} retries \u2014 CPU overloaded by parallel renders. Reduce concurrent renders or use --docker for isolation.`
|
|
20341
|
+
);
|
|
20342
|
+
}
|
|
20343
|
+
throw err;
|
|
20344
|
+
}
|
|
20345
|
+
}
|
|
20346
|
+
}
|
|
19993
20347
|
async function beginFrameCapture(page, options, frameTimeTicks, interval) {
|
|
19994
20348
|
const client = await getCdpSession(page);
|
|
19995
|
-
const
|
|
19996
|
-
const
|
|
19997
|
-
|
|
19998
|
-
|
|
19999
|
-
|
|
20000
|
-
|
|
20001
|
-
|
|
20002
|
-
optimizeForSpeed: true
|
|
20003
|
-
}
|
|
20004
|
-
});
|
|
20349
|
+
const isPng = options.format === "png";
|
|
20350
|
+
const screenshot = {
|
|
20351
|
+
format: isPng ? "png" : "jpeg",
|
|
20352
|
+
quality: isPng ? void 0 : options.quality ?? 80,
|
|
20353
|
+
optimizeForSpeed: true
|
|
20354
|
+
};
|
|
20355
|
+
const result = await sendBeginFrame(client, { frameTimeTicks, interval, screenshot });
|
|
20005
20356
|
let buffer;
|
|
20006
20357
|
if (result.screenshotData) {
|
|
20007
20358
|
buffer = Buffer.from(result.screenshotData, "base64");
|
|
@@ -20011,16 +20362,12 @@ async function beginFrameCapture(page, options, frameTimeTicks, interval) {
|
|
|
20011
20362
|
if (cached2) {
|
|
20012
20363
|
buffer = cached2;
|
|
20013
20364
|
} else {
|
|
20014
|
-
const
|
|
20365
|
+
const fallback = await sendBeginFrame(client, {
|
|
20015
20366
|
frameTimeTicks: frameTimeTicks + 1e-3,
|
|
20016
20367
|
interval,
|
|
20017
|
-
screenshot
|
|
20018
|
-
format,
|
|
20019
|
-
quality: format === "jpeg" ? options.quality ?? 80 : void 0,
|
|
20020
|
-
optimizeForSpeed: true
|
|
20021
|
-
}
|
|
20368
|
+
screenshot
|
|
20022
20369
|
});
|
|
20023
|
-
buffer =
|
|
20370
|
+
buffer = fallback.screenshotData ? Buffer.from(fallback.screenshotData, "base64") : Buffer.alloc(0);
|
|
20024
20371
|
if (buffer.length > 0) lastFrameCache.set(page, buffer);
|
|
20025
20372
|
}
|
|
20026
20373
|
}
|
|
@@ -20134,13 +20481,14 @@ async function syncVideoFrameVisibility(page, activeVideoIds) {
|
|
|
20134
20481
|
}
|
|
20135
20482
|
}, activeVideoIds);
|
|
20136
20483
|
}
|
|
20137
|
-
var cdpSessionCache, lastFrameCache;
|
|
20484
|
+
var cdpSessionCache, lastFrameCache, PENDING_FRAME_RETRIES;
|
|
20138
20485
|
var init_screenshotService = __esm({
|
|
20139
20486
|
"../engine/src/services/screenshotService.ts"() {
|
|
20140
20487
|
"use strict";
|
|
20141
20488
|
init_src();
|
|
20142
20489
|
cdpSessionCache = /* @__PURE__ */ new WeakMap();
|
|
20143
20490
|
lastFrameCache = /* @__PURE__ */ new WeakMap();
|
|
20491
|
+
PENDING_FRAME_RETRIES = 5;
|
|
20144
20492
|
}
|
|
20145
20493
|
});
|
|
20146
20494
|
|
|
@@ -20472,7 +20820,7 @@ var init_frameCapture = __esm({
|
|
|
20472
20820
|
// ../engine/src/utils/gpuEncoder.ts
|
|
20473
20821
|
import { spawn as spawn2 } from "child_process";
|
|
20474
20822
|
async function detectGpuEncoder() {
|
|
20475
|
-
return new Promise((
|
|
20823
|
+
return new Promise((resolve28) => {
|
|
20476
20824
|
const ffmpeg = spawn2("ffmpeg", ["-encoders"], {
|
|
20477
20825
|
stdio: ["pipe", "pipe", "pipe"]
|
|
20478
20826
|
});
|
|
@@ -20481,13 +20829,13 @@ async function detectGpuEncoder() {
|
|
|
20481
20829
|
stdout2 += data.toString();
|
|
20482
20830
|
});
|
|
20483
20831
|
ffmpeg.on("close", () => {
|
|
20484
|
-
if (stdout2.includes("h264_nvenc"))
|
|
20485
|
-
else if (stdout2.includes("h264_videotoolbox"))
|
|
20486
|
-
else if (stdout2.includes("h264_vaapi"))
|
|
20487
|
-
else if (stdout2.includes("h264_qsv"))
|
|
20488
|
-
else
|
|
20832
|
+
if (stdout2.includes("h264_nvenc")) resolve28("nvenc");
|
|
20833
|
+
else if (stdout2.includes("h264_videotoolbox")) resolve28("videotoolbox");
|
|
20834
|
+
else if (stdout2.includes("h264_vaapi")) resolve28("vaapi");
|
|
20835
|
+
else if (stdout2.includes("h264_qsv")) resolve28("qsv");
|
|
20836
|
+
else resolve28(null);
|
|
20489
20837
|
});
|
|
20490
|
-
ffmpeg.on("error", () =>
|
|
20838
|
+
ffmpeg.on("error", () => resolve28(null));
|
|
20491
20839
|
});
|
|
20492
20840
|
}
|
|
20493
20841
|
async function getCachedGpuEncoder() {
|
|
@@ -20526,7 +20874,7 @@ async function runFfmpeg(args, opts) {
|
|
|
20526
20874
|
const signal = opts?.signal;
|
|
20527
20875
|
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
|
|
20528
20876
|
const onStderr = opts?.onStderr;
|
|
20529
|
-
return new Promise((
|
|
20877
|
+
return new Promise((resolve28) => {
|
|
20530
20878
|
const ffmpeg = spawn3("ffmpeg", args);
|
|
20531
20879
|
let stderr = "";
|
|
20532
20880
|
const onAbort = () => {
|
|
@@ -20552,7 +20900,7 @@ async function runFfmpeg(args, opts) {
|
|
|
20552
20900
|
ffmpeg.on("close", (code) => {
|
|
20553
20901
|
clearTimeout(timer);
|
|
20554
20902
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
20555
|
-
|
|
20903
|
+
resolve28({
|
|
20556
20904
|
success: !signal?.aborted && code === 0,
|
|
20557
20905
|
exitCode: code,
|
|
20558
20906
|
stderr,
|
|
@@ -20562,7 +20910,7 @@ async function runFfmpeg(args, opts) {
|
|
|
20562
20910
|
ffmpeg.on("error", (err) => {
|
|
20563
20911
|
clearTimeout(timer);
|
|
20564
20912
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
20565
|
-
|
|
20913
|
+
resolve28({
|
|
20566
20914
|
success: false,
|
|
20567
20915
|
exitCode: null,
|
|
20568
20916
|
stderr: err.message,
|
|
@@ -20721,7 +21069,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
20721
21069
|
const inputPath = join17(framesDir, framePattern);
|
|
20722
21070
|
const inputArgs = ["-framerate", String(options.fps), "-i", inputPath];
|
|
20723
21071
|
const args = buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder);
|
|
20724
|
-
return new Promise((
|
|
21072
|
+
return new Promise((resolve28) => {
|
|
20725
21073
|
const ffmpeg = spawn4("ffmpeg", args);
|
|
20726
21074
|
let stderr = "";
|
|
20727
21075
|
const onAbort = () => {
|
|
@@ -20746,7 +21094,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
20746
21094
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
20747
21095
|
const durationMs = Date.now() - startTime;
|
|
20748
21096
|
if (signal?.aborted) {
|
|
20749
|
-
|
|
21097
|
+
resolve28({
|
|
20750
21098
|
success: false,
|
|
20751
21099
|
outputPath,
|
|
20752
21100
|
durationMs,
|
|
@@ -20757,7 +21105,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
20757
21105
|
return;
|
|
20758
21106
|
}
|
|
20759
21107
|
if (code !== 0) {
|
|
20760
|
-
|
|
21108
|
+
resolve28({
|
|
20761
21109
|
success: false,
|
|
20762
21110
|
outputPath,
|
|
20763
21111
|
durationMs,
|
|
@@ -20768,12 +21116,12 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
20768
21116
|
return;
|
|
20769
21117
|
}
|
|
20770
21118
|
const fileSize = existsSync15(outputPath) ? statSync4(outputPath).size : 0;
|
|
20771
|
-
|
|
21119
|
+
resolve28({ success: true, outputPath, durationMs, framesEncoded: frameCount, fileSize });
|
|
20772
21120
|
});
|
|
20773
21121
|
ffmpeg.on("error", (err) => {
|
|
20774
21122
|
clearTimeout(timer);
|
|
20775
21123
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
20776
|
-
|
|
21124
|
+
resolve28({
|
|
20777
21125
|
success: false,
|
|
20778
21126
|
outputPath,
|
|
20779
21127
|
durationMs: Date.now() - startTime,
|
|
@@ -20831,18 +21179,18 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
20831
21179
|
let gpuEncoder = null;
|
|
20832
21180
|
if (options.useGpu) gpuEncoder = await getCachedGpuEncoder();
|
|
20833
21181
|
const args = buildEncoderArgs(options, inputArgs, chunkPath, gpuEncoder);
|
|
20834
|
-
const chunkResult = await new Promise((
|
|
21182
|
+
const chunkResult = await new Promise((resolve28) => {
|
|
20835
21183
|
const ffmpeg = spawn4("ffmpeg", args);
|
|
20836
21184
|
let stderr = "";
|
|
20837
21185
|
ffmpeg.stderr.on("data", (d) => {
|
|
20838
21186
|
stderr += d.toString();
|
|
20839
21187
|
});
|
|
20840
21188
|
ffmpeg.on("close", (code) => {
|
|
20841
|
-
if (code === 0)
|
|
20842
|
-
else
|
|
21189
|
+
if (code === 0) resolve28({ success: true });
|
|
21190
|
+
else resolve28({ success: false, error: `Chunk ${i} encode failed: ${stderr.slice(-400)}` });
|
|
20843
21191
|
});
|
|
20844
21192
|
ffmpeg.on("error", (err) => {
|
|
20845
|
-
|
|
21193
|
+
resolve28({ success: false, error: `Chunk ${i} encode error: ${err.message}` });
|
|
20846
21194
|
});
|
|
20847
21195
|
});
|
|
20848
21196
|
if (!chunkResult.success) {
|
|
@@ -20872,18 +21220,18 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
20872
21220
|
"-y",
|
|
20873
21221
|
outputPath
|
|
20874
21222
|
];
|
|
20875
|
-
const concatResult = await new Promise((
|
|
21223
|
+
const concatResult = await new Promise((resolve28) => {
|
|
20876
21224
|
const ffmpeg = spawn4("ffmpeg", concatArgs);
|
|
20877
21225
|
let stderr = "";
|
|
20878
21226
|
ffmpeg.stderr.on("data", (d) => {
|
|
20879
21227
|
stderr += d.toString();
|
|
20880
21228
|
});
|
|
20881
21229
|
ffmpeg.on("close", (code) => {
|
|
20882
|
-
if (code === 0)
|
|
20883
|
-
else
|
|
21230
|
+
if (code === 0) resolve28({ success: true });
|
|
21231
|
+
else resolve28({ success: false, error: `Chunk concat failed: ${stderr.slice(-400)}` });
|
|
20884
21232
|
});
|
|
20885
21233
|
ffmpeg.on("error", (err) => {
|
|
20886
|
-
|
|
21234
|
+
resolve28({ success: false, error: `Chunk concat error: ${err.message}` });
|
|
20887
21235
|
});
|
|
20888
21236
|
});
|
|
20889
21237
|
if (!concatResult.success) {
|
|
@@ -20991,16 +21339,16 @@ function createFrameReorderBuffer(startFrame, endFrame) {
|
|
|
20991
21339
|
}
|
|
20992
21340
|
};
|
|
20993
21341
|
return {
|
|
20994
|
-
waitForFrame: (frame) => new Promise((
|
|
20995
|
-
waiters.push({ frame, resolve:
|
|
21342
|
+
waitForFrame: (frame) => new Promise((resolve28) => {
|
|
21343
|
+
waiters.push({ frame, resolve: resolve28 });
|
|
20996
21344
|
resolveWaiters();
|
|
20997
21345
|
}),
|
|
20998
21346
|
advanceTo: (frame) => {
|
|
20999
21347
|
nextFrame = frame;
|
|
21000
21348
|
resolveWaiters();
|
|
21001
21349
|
},
|
|
21002
|
-
waitForAllDone: () => new Promise((
|
|
21003
|
-
waiters.push({ frame: endFrame, resolve:
|
|
21350
|
+
waitForAllDone: () => new Promise((resolve28) => {
|
|
21351
|
+
waiters.push({ frame: endFrame, resolve: resolve28 });
|
|
21004
21352
|
resolveWaiters();
|
|
21005
21353
|
})
|
|
21006
21354
|
};
|
|
@@ -21129,7 +21477,7 @@ async function spawnStreamingEncoder(outputPath, options, signal, config) {
|
|
|
21129
21477
|
let stderr = "";
|
|
21130
21478
|
let exitCode = null;
|
|
21131
21479
|
let exitPromiseResolve = null;
|
|
21132
|
-
const exitPromise = new Promise((
|
|
21480
|
+
const exitPromise = new Promise((resolve28) => exitPromiseResolve = resolve28);
|
|
21133
21481
|
ffmpeg.stderr?.on("data", (data) => {
|
|
21134
21482
|
stderr += data.toString();
|
|
21135
21483
|
});
|
|
@@ -21173,8 +21521,8 @@ Process error: ${err.message}`;
|
|
|
21173
21521
|
clearTimeout(timer);
|
|
21174
21522
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
21175
21523
|
if (ffmpeg.stdin && !ffmpeg.stdin.destroyed) {
|
|
21176
|
-
await new Promise((
|
|
21177
|
-
ffmpeg.stdin.end(() =>
|
|
21524
|
+
await new Promise((resolve28) => {
|
|
21525
|
+
ffmpeg.stdin.end(() => resolve28());
|
|
21178
21526
|
});
|
|
21179
21527
|
}
|
|
21180
21528
|
await exitPromise;
|
|
@@ -21213,7 +21561,7 @@ var init_streamingEncoder = __esm({
|
|
|
21213
21561
|
// ../engine/src/utils/ffprobe.ts
|
|
21214
21562
|
import { spawn as spawn6 } from "child_process";
|
|
21215
21563
|
function runFfprobe(args) {
|
|
21216
|
-
return new Promise((
|
|
21564
|
+
return new Promise((resolve28, reject) => {
|
|
21217
21565
|
const proc = spawn6("ffprobe", args);
|
|
21218
21566
|
let stdout2 = "";
|
|
21219
21567
|
let stderr = "";
|
|
@@ -21227,7 +21575,7 @@ function runFfprobe(args) {
|
|
|
21227
21575
|
if (code !== 0) {
|
|
21228
21576
|
reject(new Error(`[FFmpeg] ffprobe exited with code ${code}: ${stderr}`));
|
|
21229
21577
|
} else {
|
|
21230
|
-
|
|
21578
|
+
resolve28(stdout2);
|
|
21231
21579
|
}
|
|
21232
21580
|
});
|
|
21233
21581
|
proc.on("error", (err) => {
|
|
@@ -21524,7 +21872,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
21524
21872
|
];
|
|
21525
21873
|
if (format === "png") args.push("-compression_level", "6");
|
|
21526
21874
|
args.push("-y", outputPattern);
|
|
21527
|
-
return new Promise((
|
|
21875
|
+
return new Promise((resolve28, reject) => {
|
|
21528
21876
|
const ffmpeg = spawn7("ffmpeg", args);
|
|
21529
21877
|
let stderr = "";
|
|
21530
21878
|
const onAbort = () => {
|
|
@@ -21559,7 +21907,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
21559
21907
|
files.forEach((file, index) => {
|
|
21560
21908
|
framePaths.set(index, join19(videoOutputDir, file));
|
|
21561
21909
|
});
|
|
21562
|
-
|
|
21910
|
+
resolve28({
|
|
21563
21911
|
videoId,
|
|
21564
21912
|
srcPath: videoPath,
|
|
21565
21913
|
outputDir: videoOutputDir,
|
|
@@ -21581,7 +21929,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
21581
21929
|
});
|
|
21582
21930
|
});
|
|
21583
21931
|
}
|
|
21584
|
-
async function extractAllVideoFrames(videos, baseDir, options, signal, config) {
|
|
21932
|
+
async function extractAllVideoFrames(videos, baseDir, options, signal, config, compiledDir) {
|
|
21585
21933
|
const startTime = Date.now();
|
|
21586
21934
|
const extracted = [];
|
|
21587
21935
|
const errors = [];
|
|
@@ -21594,7 +21942,8 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config) {
|
|
|
21594
21942
|
try {
|
|
21595
21943
|
let videoPath = video.src;
|
|
21596
21944
|
if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
|
|
21597
|
-
|
|
21945
|
+
const fromCompiled = compiledDir ? join19(compiledDir, videoPath) : null;
|
|
21946
|
+
videoPath = fromCompiled && existsSync18(fromCompiled) ? fromCompiled : join19(baseDir, videoPath);
|
|
21598
21947
|
}
|
|
21599
21948
|
if (isHttpUrl(videoPath)) {
|
|
21600
21949
|
const downloadDir = join19(options.outputDir, "_downloads");
|
|
@@ -22072,7 +22421,7 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
|
|
|
22072
22421
|
tracksProcessed: tracks.length
|
|
22073
22422
|
};
|
|
22074
22423
|
}
|
|
22075
|
-
async function processCompositionAudio(elements, baseDir, workDir, outputPath, totalDuration, signal, config) {
|
|
22424
|
+
async function processCompositionAudio(elements, baseDir, workDir, outputPath, totalDuration, signal, config, compiledDir) {
|
|
22076
22425
|
const startMs = Date.now();
|
|
22077
22426
|
const tracks = [];
|
|
22078
22427
|
const errors = [];
|
|
@@ -22086,7 +22435,8 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
22086
22435
|
try {
|
|
22087
22436
|
let srcPath = element.src;
|
|
22088
22437
|
if (!srcPath.startsWith("/") && !isHttpUrl(srcPath)) {
|
|
22089
|
-
|
|
22438
|
+
const fromCompiled = compiledDir ? join20(compiledDir, srcPath) : null;
|
|
22439
|
+
srcPath = fromCompiled && existsSync19(fromCompiled) ? fromCompiled : join20(baseDir, srcPath);
|
|
22090
22440
|
}
|
|
22091
22441
|
if (isHttpUrl(srcPath)) {
|
|
22092
22442
|
try {
|
|
@@ -22099,7 +22449,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
22099
22449
|
}
|
|
22100
22450
|
}
|
|
22101
22451
|
if (!existsSync19(srcPath)) {
|
|
22102
|
-
errors.push(`Source not found: ${element.id}`);
|
|
22452
|
+
errors.push(`Source not found: ${element.id} (${element.src})`);
|
|
22103
22453
|
return;
|
|
22104
22454
|
}
|
|
22105
22455
|
if (element.end - element.start <= 0) {
|
|
@@ -22460,11 +22810,11 @@ function createFileServer(options) {
|
|
|
22460
22810
|
headers: { "Content-Type": contentType }
|
|
22461
22811
|
});
|
|
22462
22812
|
});
|
|
22463
|
-
return new Promise((
|
|
22813
|
+
return new Promise((resolve28) => {
|
|
22464
22814
|
const server = serve({ fetch: app.fetch, port }, (info) => {
|
|
22465
22815
|
const actualPort = info.port;
|
|
22466
22816
|
const url = `http://localhost:${actualPort}`;
|
|
22467
|
-
|
|
22817
|
+
resolve28({
|
|
22468
22818
|
url,
|
|
22469
22819
|
port: actualPort,
|
|
22470
22820
|
close: () => server.close()
|
|
@@ -22578,9 +22928,9 @@ var init_src2 = __esm({
|
|
|
22578
22928
|
});
|
|
22579
22929
|
|
|
22580
22930
|
// ../core/src/compiler/htmlCompiler.ts
|
|
22581
|
-
import { resolve as
|
|
22931
|
+
import { resolve as resolve8 } from "path";
|
|
22582
22932
|
function resolveMediaSrc(src, projectDir) {
|
|
22583
|
-
return src.startsWith("http://") || src.startsWith("https://") ? src :
|
|
22933
|
+
return src.startsWith("http://") || src.startsWith("https://") ? src : resolve8(projectDir, src);
|
|
22584
22934
|
}
|
|
22585
22935
|
async function compileHtml(rawHtml, projectDir, probeMediaDuration) {
|
|
22586
22936
|
const { html: staticCompiled, unresolved } = compileTimingAttrs(rawHtml);
|
|
@@ -22659,7 +23009,7 @@ var init_staticGuard = __esm({
|
|
|
22659
23009
|
|
|
22660
23010
|
// ../core/src/compiler/htmlBundler.ts
|
|
22661
23011
|
import { readFileSync as readFileSync14, existsSync as existsSync22 } from "fs";
|
|
22662
|
-
import { join as join23, resolve as
|
|
23012
|
+
import { join as join23, resolve as resolve9, isAbsolute, sep as sep2 } from "path";
|
|
22663
23013
|
import { transformSync } from "esbuild";
|
|
22664
23014
|
function parseHTMLContent(html) {
|
|
22665
23015
|
const trimmed = html.trimStart().toLowerCase();
|
|
@@ -22669,9 +23019,9 @@ function parseHTMLContent(html) {
|
|
|
22669
23019
|
return parseHTML(`<!DOCTYPE html><html><head></head><body>${html}</body></html>`).document;
|
|
22670
23020
|
}
|
|
22671
23021
|
function safePath(projectDir, relativePath) {
|
|
22672
|
-
const resolved =
|
|
22673
|
-
const normalizedBase =
|
|
22674
|
-
if (!resolved.startsWith(normalizedBase) && resolved !==
|
|
23022
|
+
const resolved = resolve9(projectDir, relativePath);
|
|
23023
|
+
const normalizedBase = resolve9(projectDir) + sep2;
|
|
23024
|
+
if (!resolved.startsWith(normalizedBase) && resolved !== resolve9(projectDir)) return null;
|
|
22675
23025
|
return resolved;
|
|
22676
23026
|
}
|
|
22677
23027
|
function stripEmbeddedRuntimeScripts2(html) {
|
|
@@ -23206,7 +23556,7 @@ var init_compiler = __esm({
|
|
|
23206
23556
|
// ../producer/src/services/hyperframeRuntimeLoader.ts
|
|
23207
23557
|
import { createHash as createHash2 } from "crypto";
|
|
23208
23558
|
import { existsSync as existsSync23, readFileSync as readFileSync15 } from "fs";
|
|
23209
|
-
import { dirname as dirname7, resolve as
|
|
23559
|
+
import { dirname as dirname7, resolve as resolve10 } from "path";
|
|
23210
23560
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
23211
23561
|
function resolveHyperframeManifestPath() {
|
|
23212
23562
|
if (process.env.PRODUCER_HYPERFRAME_MANIFEST_PATH) {
|
|
@@ -23242,7 +23592,7 @@ function resolveVerifiedHyperframeRuntime() {
|
|
|
23242
23592
|
`[HyperframeRuntimeLoader] Invalid manifest at ${manifestPath}; missing iife artifact or sha256.`
|
|
23243
23593
|
);
|
|
23244
23594
|
}
|
|
23245
|
-
const runtimePath =
|
|
23595
|
+
const runtimePath = resolve10(dirname7(manifestPath), runtimeFileName);
|
|
23246
23596
|
if (!existsSync23(runtimePath)) {
|
|
23247
23597
|
throw new Error(`[HyperframeRuntimeLoader] Missing runtime artifact at ${runtimePath}.`);
|
|
23248
23598
|
}
|
|
@@ -23266,18 +23616,18 @@ var init_hyperframeRuntimeLoader = __esm({
|
|
|
23266
23616
|
"../producer/src/services/hyperframeRuntimeLoader.ts"() {
|
|
23267
23617
|
"use strict";
|
|
23268
23618
|
PRODUCER_DIR = dirname7(fileURLToPath2(import.meta.url));
|
|
23269
|
-
SIBLING_MANIFEST_PATH =
|
|
23270
|
-
MODULE_RELATIVE_MANIFEST_PATH =
|
|
23619
|
+
SIBLING_MANIFEST_PATH = resolve10(PRODUCER_DIR, "hyperframe.manifest.json");
|
|
23620
|
+
MODULE_RELATIVE_MANIFEST_PATH = resolve10(
|
|
23271
23621
|
PRODUCER_DIR,
|
|
23272
23622
|
"../../../core/dist/hyperframe.manifest.json"
|
|
23273
23623
|
);
|
|
23274
23624
|
CWD_RELATIVE_MANIFEST_PATHS = [
|
|
23275
23625
|
// When bundled to a single file (dist/public-server.js), the manifest
|
|
23276
23626
|
// is copied as a sibling by build.mjs
|
|
23277
|
-
|
|
23278
|
-
|
|
23279
|
-
|
|
23280
|
-
|
|
23627
|
+
resolve10(PRODUCER_DIR, "hyperframe.manifest.json"),
|
|
23628
|
+
resolve10(process.cwd(), "packages/core/dist/hyperframe.manifest.json"),
|
|
23629
|
+
resolve10(process.cwd(), "../core/dist/hyperframe.manifest.json"),
|
|
23630
|
+
resolve10(process.cwd(), "core/dist/hyperframe.manifest.json")
|
|
23281
23631
|
];
|
|
23282
23632
|
}
|
|
23283
23633
|
});
|
|
@@ -23380,10 +23730,10 @@ function createFileServer2(options) {
|
|
|
23380
23730
|
headers: { "Content-Type": contentType }
|
|
23381
23731
|
});
|
|
23382
23732
|
});
|
|
23383
|
-
return new Promise((
|
|
23733
|
+
return new Promise((resolve28) => {
|
|
23384
23734
|
const connections = /* @__PURE__ */ new Set();
|
|
23385
23735
|
const server = serve2({ fetch: app.fetch, port }, (info) => {
|
|
23386
|
-
|
|
23736
|
+
resolve28({
|
|
23387
23737
|
url: `http://localhost:${info.port}`,
|
|
23388
23738
|
port: info.port,
|
|
23389
23739
|
close: () => {
|
|
@@ -23906,7 +24256,7 @@ var init_deterministicFonts = __esm({
|
|
|
23906
24256
|
|
|
23907
24257
|
// ../producer/src/services/htmlCompiler.ts
|
|
23908
24258
|
import { readFileSync as readFileSync17, existsSync as existsSync25, mkdirSync as mkdirSync15 } from "fs";
|
|
23909
|
-
import { join as join25, dirname as dirname8, resolve as
|
|
24259
|
+
import { join as join25, dirname as dirname8, resolve as resolve11 } from "path";
|
|
23910
24260
|
import postcss from "postcss";
|
|
23911
24261
|
function dedupeElementsById(elements) {
|
|
23912
24262
|
const deduped = /* @__PURE__ */ new Map();
|
|
@@ -23991,7 +24341,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, parentOffset
|
|
|
23991
24341
|
const elEnd = elEndRaw ? parseFloat(elEndRaw) : Infinity;
|
|
23992
24342
|
const absoluteStart = parentOffset + elStart;
|
|
23993
24343
|
const absoluteEnd = Math.min(parentEnd, isFinite(elEnd) ? parentOffset + elEnd : Infinity);
|
|
23994
|
-
const filePath =
|
|
24344
|
+
const filePath = resolve11(projectDir, srcPath);
|
|
23995
24345
|
if (visited.has(filePath)) {
|
|
23996
24346
|
continue;
|
|
23997
24347
|
}
|
|
@@ -24191,7 +24541,7 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
|
|
|
24191
24541
|
if (!srcPath) continue;
|
|
24192
24542
|
let compHtml = subCompositions.get(srcPath) || null;
|
|
24193
24543
|
if (!compHtml) {
|
|
24194
|
-
const filePath =
|
|
24544
|
+
const filePath = resolve11(projectDir, srcPath);
|
|
24195
24545
|
if (existsSync25(filePath)) {
|
|
24196
24546
|
compHtml = readFileSync17(filePath, "utf-8");
|
|
24197
24547
|
}
|
|
@@ -24404,7 +24754,7 @@ ${safeText}
|
|
|
24404
24754
|
return result;
|
|
24405
24755
|
}
|
|
24406
24756
|
function collectExternalAssets(html, projectDir) {
|
|
24407
|
-
const absProjectDir =
|
|
24757
|
+
const absProjectDir = resolve11(projectDir);
|
|
24408
24758
|
const externalAssets = /* @__PURE__ */ new Map();
|
|
24409
24759
|
const CSS_URL_RE2 = /\burl\(\s*(["']?)([^)"']+)\1\s*\)/g;
|
|
24410
24760
|
function processPath(rawPath) {
|
|
@@ -24412,7 +24762,7 @@ function collectExternalAssets(html, projectDir) {
|
|
|
24412
24762
|
if (!trimmed || trimmed.startsWith("/") || trimmed.startsWith("http://") || trimmed.startsWith("https://") || trimmed.startsWith("//") || trimmed.startsWith("data:") || trimmed.startsWith("#")) {
|
|
24413
24763
|
return null;
|
|
24414
24764
|
}
|
|
24415
|
-
const absPath =
|
|
24765
|
+
const absPath = resolve11(absProjectDir, trimmed);
|
|
24416
24766
|
if (absPath.startsWith(absProjectDir + "/") || absPath === absProjectDir) {
|
|
24417
24767
|
return null;
|
|
24418
24768
|
}
|
|
@@ -24489,7 +24839,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
|
|
|
24489
24839
|
const audios = dedupeElementsById([...mainAudios, ...subAudios]);
|
|
24490
24840
|
for (const video of videos) {
|
|
24491
24841
|
if (isHttpUrl(video.src)) continue;
|
|
24492
|
-
const videoPath =
|
|
24842
|
+
const videoPath = resolve11(projectDir, video.src);
|
|
24493
24843
|
const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
|
|
24494
24844
|
Promise.all([analyzeKeyframeIntervals(videoPath), extractVideoMetadata(videoPath)]).then(([analysis, metadata]) => {
|
|
24495
24845
|
if (analysis.isProblematic) {
|
|
@@ -24682,7 +25032,7 @@ import {
|
|
|
24682
25032
|
copyFileSync as copyFileSync2,
|
|
24683
25033
|
appendFileSync
|
|
24684
25034
|
} from "fs";
|
|
24685
|
-
import { join as join26, dirname as dirname9, resolve as
|
|
25035
|
+
import { join as join26, dirname as dirname9, resolve as resolve12 } from "path";
|
|
24686
25036
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
24687
25037
|
import { freemem as freemem2 } from "os";
|
|
24688
25038
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -24747,7 +25097,7 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
|
|
|
24747
25097
|
writeFileSync8(outPath, html, "utf-8");
|
|
24748
25098
|
}
|
|
24749
25099
|
for (const [relativePath, absolutePath] of compiled.externalAssets) {
|
|
24750
|
-
const outPath =
|
|
25100
|
+
const outPath = resolve12(join26(compileDir, relativePath));
|
|
24751
25101
|
if (!outPath.startsWith(compileDir + "/")) {
|
|
24752
25102
|
console.warn(`[Render] Skipping external asset with unsafe path: ${relativePath}`);
|
|
24753
25103
|
continue;
|
|
@@ -24820,7 +25170,7 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
|
|
|
24820
25170
|
}
|
|
24821
25171
|
async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
|
|
24822
25172
|
const moduleDir = dirname9(fileURLToPath3(import.meta.url));
|
|
24823
|
-
const producerRoot = process.env.PRODUCER_RENDERS_DIR ?
|
|
25173
|
+
const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve12(process.env.PRODUCER_RENDERS_DIR, "..") : resolve12(moduleDir, "../..");
|
|
24824
25174
|
const debugDir = join26(producerRoot, ".debug");
|
|
24825
25175
|
const workDir = job.config.debug ? join26(debugDir, job.id) : join26(dirname9(outputPath), `work-${job.id}`);
|
|
24826
25176
|
const pipelineStart = Date.now();
|
|
@@ -25097,12 +25447,15 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25097
25447
|
const stage2Start = Date.now();
|
|
25098
25448
|
updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress);
|
|
25099
25449
|
let frameLookup = null;
|
|
25450
|
+
const compiledDir = join26(workDir, "compiled");
|
|
25100
25451
|
if (composition.videos.length > 0) {
|
|
25101
25452
|
const extractionResult = await extractAllVideoFrames(
|
|
25102
25453
|
composition.videos,
|
|
25103
25454
|
projectDir,
|
|
25104
25455
|
{ fps: job.config.fps, outputDir: join26(workDir, "video-frames") },
|
|
25105
|
-
abortSignal
|
|
25456
|
+
abortSignal,
|
|
25457
|
+
void 0,
|
|
25458
|
+
compiledDir
|
|
25106
25459
|
);
|
|
25107
25460
|
assertNotAborted();
|
|
25108
25461
|
if (extractionResult.extracted.length > 0) {
|
|
@@ -25142,7 +25495,9 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25142
25495
|
join26(workDir, "audio-work"),
|
|
25143
25496
|
audioOutputPath,
|
|
25144
25497
|
job.duration,
|
|
25145
|
-
abortSignal
|
|
25498
|
+
abortSignal,
|
|
25499
|
+
void 0,
|
|
25500
|
+
compiledDir
|
|
25146
25501
|
);
|
|
25147
25502
|
assertNotAborted();
|
|
25148
25503
|
hasAudio = audioResult.success;
|
|
@@ -25589,7 +25944,7 @@ var init_config3 = __esm({
|
|
|
25589
25944
|
|
|
25590
25945
|
// ../producer/src/services/hyperframeLint.ts
|
|
25591
25946
|
import { existsSync as existsSync27, readFileSync as readFileSync19, statSync as statSync8 } from "fs";
|
|
25592
|
-
import { resolve as
|
|
25947
|
+
import { resolve as resolve13, join as join27 } from "path";
|
|
25593
25948
|
function isStringRecord(value) {
|
|
25594
25949
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
25595
25950
|
return false;
|
|
@@ -25616,7 +25971,7 @@ function pickEntryFile(files, preferredEntryFile) {
|
|
|
25616
25971
|
return null;
|
|
25617
25972
|
}
|
|
25618
25973
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
25619
|
-
const absProjectDir =
|
|
25974
|
+
const absProjectDir = resolve13(projectDir);
|
|
25620
25975
|
if (!existsSync27(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
|
|
25621
25976
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
25622
25977
|
}
|
|
@@ -25624,7 +25979,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
25624
25979
|
(value) => typeof value === "string" && value.trim().length > 0
|
|
25625
25980
|
);
|
|
25626
25981
|
for (const entryFile of entryCandidates) {
|
|
25627
|
-
const absoluteEntryPath =
|
|
25982
|
+
const absoluteEntryPath = resolve13(absProjectDir, entryFile);
|
|
25628
25983
|
if (!absoluteEntryPath.startsWith(absProjectDir)) {
|
|
25629
25984
|
return { error: `Entry file must stay inside project directory: ${entryFile}` };
|
|
25630
25985
|
}
|
|
@@ -25684,19 +26039,59 @@ var init_hyperframeLint = __esm({
|
|
|
25684
26039
|
});
|
|
25685
26040
|
|
|
25686
26041
|
// ../producer/src/utils/paths.ts
|
|
25687
|
-
import { resolve as
|
|
26042
|
+
import { resolve as resolve14, basename, join as join28 } from "path";
|
|
25688
26043
|
function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS_DIR) {
|
|
25689
|
-
const absoluteProjectDir =
|
|
26044
|
+
const absoluteProjectDir = resolve14(projectDir);
|
|
25690
26045
|
const projectName = basename(absoluteProjectDir);
|
|
25691
26046
|
const resolvedOutputPath = outputPath ?? join28(rendersDir, `${projectName}.mp4`);
|
|
25692
|
-
const absoluteOutputPath =
|
|
26047
|
+
const absoluteOutputPath = resolve14(resolvedOutputPath);
|
|
25693
26048
|
return { absoluteProjectDir, absoluteOutputPath };
|
|
25694
26049
|
}
|
|
25695
26050
|
var DEFAULT_RENDERS_DIR;
|
|
25696
26051
|
var init_paths = __esm({
|
|
25697
26052
|
"../producer/src/utils/paths.ts"() {
|
|
25698
26053
|
"use strict";
|
|
25699
|
-
DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ??
|
|
26054
|
+
DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? resolve14(new URL(import.meta.url).pathname, "../../..", "renders");
|
|
26055
|
+
}
|
|
26056
|
+
});
|
|
26057
|
+
|
|
26058
|
+
// ../producer/src/utils/semaphore.ts
|
|
26059
|
+
var Semaphore;
|
|
26060
|
+
var init_semaphore = __esm({
|
|
26061
|
+
"../producer/src/utils/semaphore.ts"() {
|
|
26062
|
+
"use strict";
|
|
26063
|
+
Semaphore = class {
|
|
26064
|
+
constructor(maxConcurrent) {
|
|
26065
|
+
this.maxConcurrent = maxConcurrent;
|
|
26066
|
+
}
|
|
26067
|
+
queue = [];
|
|
26068
|
+
active = 0;
|
|
26069
|
+
async acquire() {
|
|
26070
|
+
if (this.active < this.maxConcurrent) {
|
|
26071
|
+
this.active++;
|
|
26072
|
+
return () => this.release();
|
|
26073
|
+
}
|
|
26074
|
+
return new Promise((resolve28) => {
|
|
26075
|
+
this.queue.push(() => {
|
|
26076
|
+
this.active++;
|
|
26077
|
+
resolve28(() => this.release());
|
|
26078
|
+
});
|
|
26079
|
+
});
|
|
26080
|
+
}
|
|
26081
|
+
release() {
|
|
26082
|
+
this.active--;
|
|
26083
|
+
const next = this.queue.shift();
|
|
26084
|
+
if (next) next();
|
|
26085
|
+
}
|
|
26086
|
+
/** Current number of active slots. */
|
|
26087
|
+
get activeCount() {
|
|
26088
|
+
return this.active;
|
|
26089
|
+
}
|
|
26090
|
+
/** Number of waiters in the queue. */
|
|
26091
|
+
get waitingCount() {
|
|
26092
|
+
return this.queue.length;
|
|
26093
|
+
}
|
|
26094
|
+
};
|
|
25700
26095
|
}
|
|
25701
26096
|
});
|
|
25702
26097
|
|
|
@@ -25710,7 +26105,7 @@ import {
|
|
|
25710
26105
|
rmSync as rmSync7,
|
|
25711
26106
|
createReadStream
|
|
25712
26107
|
} from "fs";
|
|
25713
|
-
import { resolve as
|
|
26108
|
+
import { resolve as resolve15, dirname as dirname10, join as join29 } from "path";
|
|
25714
26109
|
import { tmpdir as tmpdir2 } from "os";
|
|
25715
26110
|
import { parseArgs as parseArgs2 } from "util";
|
|
25716
26111
|
import crypto from "crypto";
|
|
@@ -25732,12 +26127,12 @@ async function prepareRenderBody(body) {
|
|
|
25732
26127
|
const options = parseRenderOptions(body);
|
|
25733
26128
|
const projectDir = typeof body.projectDir === "string" ? body.projectDir : void 0;
|
|
25734
26129
|
if (projectDir) {
|
|
25735
|
-
const absProjectDir =
|
|
26130
|
+
const absProjectDir = resolve15(projectDir);
|
|
25736
26131
|
if (!existsSync28(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
|
|
25737
26132
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
25738
26133
|
}
|
|
25739
26134
|
const entry = options.entryFile || "index.html";
|
|
25740
|
-
if (!existsSync28(
|
|
26135
|
+
if (!existsSync28(resolve15(absProjectDir, entry))) {
|
|
25741
26136
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
25742
26137
|
}
|
|
25743
26138
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -25778,7 +26173,7 @@ function resolveOutputPath(projectDir, outputCandidate, rendersDir, log) {
|
|
|
25778
26173
|
try {
|
|
25779
26174
|
return resolveRenderPaths(projectDir, outputCandidate, rendersDir).absoluteOutputPath;
|
|
25780
26175
|
} catch (error) {
|
|
25781
|
-
const fallbackPath =
|
|
26176
|
+
const fallbackPath = resolve15(rendersDir, `producer-fallback-${Date.now()}.mp4`);
|
|
25782
26177
|
log.warn("Failed to resolve output path, using fallback", {
|
|
25783
26178
|
fallback: fallbackPath,
|
|
25784
26179
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -25829,6 +26224,8 @@ function createRenderHandlers(options = {}) {
|
|
|
25829
26224
|
const rendersDir = options.rendersDir ?? process.env.PRODUCER_RENDERS_DIR ?? "/tmp";
|
|
25830
26225
|
const artifactTtlMs = options.artifactTtlMs ?? Number(process.env.PRODUCER_OUTPUT_ARTIFACT_TTL_MS || 15 * 60 * 1e3);
|
|
25831
26226
|
const store = createArtifactStore(artifactTtlMs);
|
|
26227
|
+
const maxConcurrentRenders = options.maxConcurrentRenders ?? Number(process.env.PRODUCER_MAX_CONCURRENT_RENDERS || 2);
|
|
26228
|
+
const renderSemaphore = new Semaphore(maxConcurrentRenders);
|
|
25832
26229
|
const startTime = Date.now();
|
|
25833
26230
|
const health = (c2) => c2.json({
|
|
25834
26231
|
status: "ok",
|
|
@@ -25885,6 +26282,7 @@ function createRenderHandlers(options = {}) {
|
|
|
25885
26282
|
);
|
|
25886
26283
|
const outputDir = dirname10(absoluteOutputPath);
|
|
25887
26284
|
if (!existsSync28(outputDir)) mkdirSync17(outputDir, { recursive: true });
|
|
26285
|
+
const release2 = await renderSemaphore.acquire();
|
|
25888
26286
|
log.info("render started", {
|
|
25889
26287
|
requestId,
|
|
25890
26288
|
projectDir: input.projectDir,
|
|
@@ -25952,6 +26350,7 @@ function createRenderHandlers(options = {}) {
|
|
|
25952
26350
|
500
|
|
25953
26351
|
);
|
|
25954
26352
|
} finally {
|
|
26353
|
+
release2();
|
|
25955
26354
|
cleanupTempDir(cleanupProjectDir, log);
|
|
25956
26355
|
}
|
|
25957
26356
|
};
|
|
@@ -26008,6 +26407,16 @@ function createRenderHandlers(options = {}) {
|
|
|
26008
26407
|
const abortController = new AbortController();
|
|
26009
26408
|
const onRequestAbort = () => abortController.abort(new RenderCancelledError("request_aborted"));
|
|
26010
26409
|
c2.req.raw.signal.addEventListener("abort", onRequestAbort, { once: true });
|
|
26410
|
+
if (renderSemaphore.activeCount >= maxConcurrentRenders) {
|
|
26411
|
+
await stream.writeSSE({
|
|
26412
|
+
data: JSON.stringify({
|
|
26413
|
+
type: "queued",
|
|
26414
|
+
requestId,
|
|
26415
|
+
position: renderSemaphore.waitingCount
|
|
26416
|
+
})
|
|
26417
|
+
});
|
|
26418
|
+
}
|
|
26419
|
+
const release2 = await renderSemaphore.acquire();
|
|
26011
26420
|
try {
|
|
26012
26421
|
await executeRenderJob(
|
|
26013
26422
|
job,
|
|
@@ -26075,6 +26484,7 @@ function createRenderHandlers(options = {}) {
|
|
|
26075
26484
|
})
|
|
26076
26485
|
});
|
|
26077
26486
|
} finally {
|
|
26487
|
+
release2();
|
|
26078
26488
|
c2.req.raw.signal.removeEventListener("abort", onRequestAbort);
|
|
26079
26489
|
cleanupTempDir(cleanupProjectDir, log);
|
|
26080
26490
|
}
|
|
@@ -26099,7 +26509,12 @@ function createRenderHandlers(options = {}) {
|
|
|
26099
26509
|
}
|
|
26100
26510
|
});
|
|
26101
26511
|
};
|
|
26102
|
-
|
|
26512
|
+
const queue = (c2) => c2.json({
|
|
26513
|
+
maxConcurrentRenders,
|
|
26514
|
+
activeRenders: renderSemaphore.activeCount,
|
|
26515
|
+
queuedRenders: renderSemaphore.waitingCount
|
|
26516
|
+
});
|
|
26517
|
+
return { render: render2, renderStream, lint, health, outputs, queue };
|
|
26103
26518
|
}
|
|
26104
26519
|
function createProducerApp(options = {}) {
|
|
26105
26520
|
const app = new Hono4();
|
|
@@ -26107,6 +26522,7 @@ function createProducerApp(options = {}) {
|
|
|
26107
26522
|
app.get("/health", handlers.health);
|
|
26108
26523
|
app.post("/render", handlers.render);
|
|
26109
26524
|
app.post("/render/stream", handlers.renderStream);
|
|
26525
|
+
app.get("/render/queue", handlers.queue);
|
|
26110
26526
|
app.post("/lint", handlers.lint);
|
|
26111
26527
|
app.get("/outputs/:token", handlers.outputs);
|
|
26112
26528
|
return app;
|
|
@@ -26144,7 +26560,8 @@ var init_server = __esm({
|
|
|
26144
26560
|
init_hyperframeLint();
|
|
26145
26561
|
init_paths();
|
|
26146
26562
|
init_logger();
|
|
26147
|
-
|
|
26563
|
+
init_semaphore();
|
|
26564
|
+
entryScript = process.argv[1] ? resolve15(process.argv[1]) : "";
|
|
26148
26565
|
isPublicServerEntry = entryScript.endsWith("/public-server.js") || entryScript.endsWith("/src/server.ts");
|
|
26149
26566
|
if (isPublicServerEntry) {
|
|
26150
26567
|
const { values } = parseArgs2({
|
|
@@ -26217,18 +26634,18 @@ __export(studioServer_exports, {
|
|
|
26217
26634
|
import { Hono as Hono5 } from "hono";
|
|
26218
26635
|
import { streamSSE as streamSSE3 } from "hono/streaming";
|
|
26219
26636
|
import { existsSync as existsSync29, readFileSync as readFileSync20, writeFileSync as writeFileSync10, statSync as statSync10 } from "fs";
|
|
26220
|
-
import { resolve as
|
|
26637
|
+
import { resolve as resolve16, join as join30, basename as basename2 } from "path";
|
|
26221
26638
|
function resolveDistDir() {
|
|
26222
|
-
const builtPath =
|
|
26223
|
-
if (existsSync29(
|
|
26224
|
-
const devPath =
|
|
26225
|
-
if (existsSync29(
|
|
26639
|
+
const builtPath = resolve16(__dirname, "studio");
|
|
26640
|
+
if (existsSync29(resolve16(builtPath, "index.html"))) return builtPath;
|
|
26641
|
+
const devPath = resolve16(__dirname, "..", "..", "..", "studio", "dist");
|
|
26642
|
+
if (existsSync29(resolve16(devPath, "index.html"))) return devPath;
|
|
26226
26643
|
return builtPath;
|
|
26227
26644
|
}
|
|
26228
26645
|
function resolveRuntimePath() {
|
|
26229
|
-
const builtPath =
|
|
26646
|
+
const builtPath = resolve16(__dirname, "hyperframe-runtime.js");
|
|
26230
26647
|
if (existsSync29(builtPath)) return builtPath;
|
|
26231
|
-
const devPath =
|
|
26648
|
+
const devPath = resolve16(
|
|
26232
26649
|
__dirname,
|
|
26233
26650
|
"..",
|
|
26234
26651
|
"..",
|
|
@@ -26379,6 +26796,14 @@ function createStudioServer(options) {
|
|
|
26379
26796
|
}
|
|
26380
26797
|
};
|
|
26381
26798
|
const app = new Hono5();
|
|
26799
|
+
app.get("/__hyperframes_config", (c2) => {
|
|
26800
|
+
return c2.json({
|
|
26801
|
+
isHyperframes: true,
|
|
26802
|
+
projectName: projectId,
|
|
26803
|
+
projectDir,
|
|
26804
|
+
version: VERSION
|
|
26805
|
+
});
|
|
26806
|
+
});
|
|
26382
26807
|
app.get("/api/runtime.js", (c2) => {
|
|
26383
26808
|
if (!existsSync29(runtimePath)) return c2.text("runtime not built", 404);
|
|
26384
26809
|
return c2.body(readFileSync20(runtimePath, "utf-8"), 200, {
|
|
@@ -26412,7 +26837,7 @@ function createStudioServer(options) {
|
|
|
26412
26837
|
return api.fetch(forwardReq);
|
|
26413
26838
|
});
|
|
26414
26839
|
app.get("/assets/*", (c2) => {
|
|
26415
|
-
const filePath =
|
|
26840
|
+
const filePath = resolve16(studioDir, c2.req.path.slice(1));
|
|
26416
26841
|
if (!existsSync29(filePath) || !statSync10(filePath).isFile()) return c2.text("not found", 404);
|
|
26417
26842
|
const content = readFileSync20(filePath);
|
|
26418
26843
|
return new Response(content, {
|
|
@@ -26420,7 +26845,7 @@ function createStudioServer(options) {
|
|
|
26420
26845
|
});
|
|
26421
26846
|
});
|
|
26422
26847
|
app.get("/icons/*", (c2) => {
|
|
26423
|
-
const filePath =
|
|
26848
|
+
const filePath = resolve16(studioDir, c2.req.path.slice(1));
|
|
26424
26849
|
if (!existsSync29(filePath) || !statSync10(filePath).isFile()) return c2.text("not found", 404);
|
|
26425
26850
|
const content = readFileSync20(filePath);
|
|
26426
26851
|
return new Response(content, {
|
|
@@ -26428,7 +26853,7 @@ function createStudioServer(options) {
|
|
|
26428
26853
|
});
|
|
26429
26854
|
});
|
|
26430
26855
|
app.get("*", (c2) => {
|
|
26431
|
-
const indexPath =
|
|
26856
|
+
const indexPath = resolve16(studioDir, "index.html");
|
|
26432
26857
|
if (!existsSync29(indexPath)) {
|
|
26433
26858
|
return c2.text("Studio not found. Rebuild with: pnpm run build", 500);
|
|
26434
26859
|
}
|
|
@@ -26441,6 +26866,7 @@ var init_studioServer = __esm({
|
|
|
26441
26866
|
"src/server/studioServer.ts"() {
|
|
26442
26867
|
"use strict";
|
|
26443
26868
|
init_fileWatcher();
|
|
26869
|
+
init_version();
|
|
26444
26870
|
init_studio_api();
|
|
26445
26871
|
_thumbnailBrowser = null;
|
|
26446
26872
|
_thumbnailBrowserInitializing = null;
|
|
@@ -26455,45 +26881,12 @@ __export(preview_exports, {
|
|
|
26455
26881
|
});
|
|
26456
26882
|
import { spawn as spawn8 } from "child_process";
|
|
26457
26883
|
import { existsSync as existsSync30, lstatSync, symlinkSync, unlinkSync as unlinkSync5, readlinkSync, mkdirSync as mkdirSync18 } from "fs";
|
|
26458
|
-
import { resolve as
|
|
26884
|
+
import { resolve as resolve17, dirname as dirname11, basename as basename3, join as join31 } from "path";
|
|
26459
26885
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
26460
26886
|
import { createRequire } from "module";
|
|
26461
|
-
async function serveWithPortFallback(fetch3, startPort, maxAttempts = 10) {
|
|
26462
|
-
const { createAdaptorServer } = await import("@hono/node-server");
|
|
26463
|
-
const server = createAdaptorServer({ fetch: fetch3 });
|
|
26464
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
26465
|
-
const port = startPort + attempt;
|
|
26466
|
-
try {
|
|
26467
|
-
await new Promise((resolveListener, rejectListener) => {
|
|
26468
|
-
const onError = (err) => {
|
|
26469
|
-
server.removeListener("listening", onListening);
|
|
26470
|
-
rejectListener(err);
|
|
26471
|
-
};
|
|
26472
|
-
const onListening = () => {
|
|
26473
|
-
server.removeListener("error", onError);
|
|
26474
|
-
resolveListener();
|
|
26475
|
-
};
|
|
26476
|
-
server.once("error", onError);
|
|
26477
|
-
server.once("listening", onListening);
|
|
26478
|
-
server.listen(port);
|
|
26479
|
-
});
|
|
26480
|
-
return { server, port };
|
|
26481
|
-
} catch (err) {
|
|
26482
|
-
const code = err.code;
|
|
26483
|
-
if (code === "EADDRINUSE") {
|
|
26484
|
-
continue;
|
|
26485
|
-
}
|
|
26486
|
-
throw err;
|
|
26487
|
-
}
|
|
26488
|
-
}
|
|
26489
|
-
const lastPort = startPort + maxAttempts - 1;
|
|
26490
|
-
throw new Error(
|
|
26491
|
-
`Ports ${startPort}\u2013${lastPort} are all in use. Use --port to specify a different port.`
|
|
26492
|
-
);
|
|
26493
|
-
}
|
|
26494
26887
|
async function runDevMode(dir, projectName) {
|
|
26495
26888
|
const thisFile = fileURLToPath4(import.meta.url);
|
|
26496
|
-
const repoRoot =
|
|
26889
|
+
const repoRoot = resolve17(dirname11(thisFile), "..", "..", "..", "..");
|
|
26497
26890
|
const projectsDir = join31(repoRoot, "packages", "studio", "data", "projects");
|
|
26498
26891
|
const pName = projectName ?? basename3(dir);
|
|
26499
26892
|
const symlinkPath = join31(projectsDir, pName);
|
|
@@ -26505,7 +26898,7 @@ async function runDevMode(dir, projectName) {
|
|
|
26505
26898
|
const stat = lstatSync(symlinkPath);
|
|
26506
26899
|
if (stat.isSymbolicLink()) {
|
|
26507
26900
|
const target = readlinkSync(symlinkPath);
|
|
26508
|
-
if (
|
|
26901
|
+
if (resolve17(target) !== resolve17(dir)) {
|
|
26509
26902
|
unlinkSync5(symlinkPath);
|
|
26510
26903
|
}
|
|
26511
26904
|
}
|
|
@@ -26559,8 +26952,8 @@ async function runDevMode(dir, projectName) {
|
|
|
26559
26952
|
}
|
|
26560
26953
|
});
|
|
26561
26954
|
}
|
|
26562
|
-
return new Promise((
|
|
26563
|
-
child.on("close", () =>
|
|
26955
|
+
return new Promise((resolve28) => {
|
|
26956
|
+
child.on("close", () => resolve28());
|
|
26564
26957
|
});
|
|
26565
26958
|
}
|
|
26566
26959
|
function hasLocalStudio(dir) {
|
|
@@ -26582,7 +26975,7 @@ async function runLocalStudioMode(dir, projectName) {
|
|
|
26582
26975
|
let createdSymlink = false;
|
|
26583
26976
|
if (dir !== symlinkPath) {
|
|
26584
26977
|
if (existsSync30(symlinkPath) && lstatSync(symlinkPath).isSymbolicLink()) {
|
|
26585
|
-
if (
|
|
26978
|
+
if (resolve17(readlinkSync(symlinkPath)) !== resolve17(dir)) {
|
|
26586
26979
|
unlinkSync5(symlinkPath);
|
|
26587
26980
|
}
|
|
26588
26981
|
}
|
|
@@ -26630,20 +27023,20 @@ async function runLocalStudioMode(dir, projectName) {
|
|
|
26630
27023
|
}
|
|
26631
27024
|
});
|
|
26632
27025
|
}
|
|
26633
|
-
return new Promise((
|
|
26634
|
-
child.on("close", () =>
|
|
27026
|
+
return new Promise((resolve28) => {
|
|
27027
|
+
child.on("close", () => resolve28());
|
|
26635
27028
|
});
|
|
26636
27029
|
}
|
|
26637
|
-
async function runEmbeddedMode(dir, startPort, projectName) {
|
|
27030
|
+
async function runEmbeddedMode(dir, startPort, projectName, forceNew = false) {
|
|
26638
27031
|
const { createStudioServer: createStudioServer2 } = await Promise.resolve().then(() => (init_studioServer(), studioServer_exports));
|
|
26639
27032
|
const pName = projectName ?? basename3(dir);
|
|
26640
27033
|
const { app } = createStudioServer2({ projectDir: dir, projectName: pName });
|
|
26641
27034
|
Wt2(c.bold("hyperframes preview"));
|
|
26642
27035
|
const s = be();
|
|
26643
27036
|
s.start("Starting studio...");
|
|
26644
|
-
let
|
|
27037
|
+
let result;
|
|
26645
27038
|
try {
|
|
26646
|
-
|
|
27039
|
+
result = await findPortAndServe(app.fetch, startPort, dir, forceNew);
|
|
26647
27040
|
} catch (err) {
|
|
26648
27041
|
s.stop(c.error("Failed to start studio"));
|
|
26649
27042
|
console.error();
|
|
@@ -26652,11 +27045,26 @@ async function runEmbeddedMode(dir, startPort, projectName) {
|
|
|
26652
27045
|
process.exitCode = 1;
|
|
26653
27046
|
return;
|
|
26654
27047
|
}
|
|
26655
|
-
|
|
27048
|
+
if (result.type === "already-running") {
|
|
27049
|
+
const url2 = `http://localhost:${result.port}`;
|
|
27050
|
+
s.stop(c.success("Already running"));
|
|
27051
|
+
console.log();
|
|
27052
|
+
console.log(` ${c.dim("Project")} ${c.accent(pName)}`);
|
|
27053
|
+
console.log(` ${c.dim("Studio")} ${c.accent(url2)}`);
|
|
27054
|
+
console.log();
|
|
27055
|
+
console.log(
|
|
27056
|
+
` ${c.dim("Reusing existing server. Use --force-new to start a fresh instance.")}`
|
|
27057
|
+
);
|
|
27058
|
+
console.log();
|
|
27059
|
+
import("open").then((mod) => mod.default(`${url2}#project/${pName}`)).catch(() => {
|
|
27060
|
+
});
|
|
27061
|
+
return;
|
|
27062
|
+
}
|
|
27063
|
+
const url = `http://localhost:${result.port}`;
|
|
26656
27064
|
s.stop(c.success("Studio running"));
|
|
26657
27065
|
console.log();
|
|
26658
|
-
if (
|
|
26659
|
-
console.log(` ${c.warn(`Port ${startPort} is in use, using ${
|
|
27066
|
+
if (result.port !== startPort) {
|
|
27067
|
+
console.log(` ${c.warn(`Port ${startPort} is in use, using ${result.port} instead`)}`);
|
|
26660
27068
|
console.log();
|
|
26661
27069
|
}
|
|
26662
27070
|
console.log(` ${c.dim("Project")} ${c.accent(pName)}`);
|
|
@@ -26682,21 +27090,72 @@ var init_preview2 = __esm({
|
|
|
26682
27090
|
init_env();
|
|
26683
27091
|
init_lintProject();
|
|
26684
27092
|
init_lintFormat();
|
|
27093
|
+
init_portUtils();
|
|
26685
27094
|
examples = [
|
|
26686
27095
|
["Preview the current project", "hyperframes preview"],
|
|
26687
27096
|
["Preview a specific project directory", "hyperframes preview ./my-video"],
|
|
26688
|
-
["Use a custom port", "hyperframes preview --port 8080"]
|
|
27097
|
+
["Use a custom port", "hyperframes preview --port 8080"],
|
|
27098
|
+
["Force a new server even if one is already running", "hyperframes preview --force-new"],
|
|
27099
|
+
["List all active preview servers", "hyperframes preview --list"],
|
|
27100
|
+
["Kill all active preview servers", "hyperframes preview --kill-all"]
|
|
26689
27101
|
];
|
|
26690
27102
|
preview_default = defineCommand({
|
|
26691
27103
|
meta: { name: "preview", description: "Start the studio for previewing compositions" },
|
|
26692
27104
|
args: {
|
|
26693
27105
|
dir: { type: "positional", description: "Project directory", required: false },
|
|
26694
|
-
port: { type: "string", description: "Port to run the preview server on", default: "3002" }
|
|
27106
|
+
port: { type: "string", description: "Port to run the preview server on", default: "3002" },
|
|
27107
|
+
"force-new": {
|
|
27108
|
+
type: "boolean",
|
|
27109
|
+
description: "Start a new server even if one is already running for this project",
|
|
27110
|
+
default: false
|
|
27111
|
+
},
|
|
27112
|
+
list: {
|
|
27113
|
+
type: "boolean",
|
|
27114
|
+
description: "List all active preview servers and exit",
|
|
27115
|
+
default: false
|
|
27116
|
+
},
|
|
27117
|
+
"kill-all": {
|
|
27118
|
+
type: "boolean",
|
|
27119
|
+
description: "Kill all active preview servers and exit",
|
|
27120
|
+
default: false
|
|
27121
|
+
}
|
|
26695
27122
|
},
|
|
26696
27123
|
async run({ args }) {
|
|
26697
|
-
const rawArg = args.dir;
|
|
26698
|
-
const dir = resolve16(rawArg ?? ".");
|
|
26699
27124
|
const startPort = parseInt(args.port ?? "3002", 10);
|
|
27125
|
+
if (args.list) {
|
|
27126
|
+
const servers = await scanActiveServers(startPort);
|
|
27127
|
+
if (servers.length === 0) {
|
|
27128
|
+
console.log("\n No active preview servers found.\n");
|
|
27129
|
+
return;
|
|
27130
|
+
}
|
|
27131
|
+
console.log(`
|
|
27132
|
+
${c.bold("Active preview servers:")}
|
|
27133
|
+
`);
|
|
27134
|
+
for (const s of servers) {
|
|
27135
|
+
const pidStr = s.pid ? c.dim(` (PID ${s.pid})`) : "";
|
|
27136
|
+
console.log(
|
|
27137
|
+
` ${c.accent(`Port ${s.port}`)} ${s.projectName} ${c.dim(s.projectDir)}${pidStr}`
|
|
27138
|
+
);
|
|
27139
|
+
}
|
|
27140
|
+
console.log(`
|
|
27141
|
+
${servers.length} server${servers.length === 1 ? "" : "s"} running.
|
|
27142
|
+
`);
|
|
27143
|
+
return;
|
|
27144
|
+
}
|
|
27145
|
+
if (args["kill-all"]) {
|
|
27146
|
+
const servers = await scanActiveServers(startPort);
|
|
27147
|
+
if (servers.length === 0) {
|
|
27148
|
+
console.log("\n No active preview servers to kill.\n");
|
|
27149
|
+
return;
|
|
27150
|
+
}
|
|
27151
|
+
const killed = await killActiveServers(startPort);
|
|
27152
|
+
console.log(`
|
|
27153
|
+
Killed ${killed} preview server${killed === 1 ? "" : "s"}.
|
|
27154
|
+
`);
|
|
27155
|
+
return;
|
|
27156
|
+
}
|
|
27157
|
+
const rawArg = args.dir;
|
|
27158
|
+
const dir = resolve17(rawArg ?? ".");
|
|
26700
27159
|
const isImplicitCwd = !rawArg || rawArg === "." || rawArg === "./";
|
|
26701
27160
|
const projectName = isImplicitCwd ? basename3(process.env.PWD ?? dir) : basename3(dir);
|
|
26702
27161
|
const indexPath = join31(dir, "index.html");
|
|
@@ -26715,7 +27174,8 @@ var init_preview2 = __esm({
|
|
|
26715
27174
|
if (hasLocalStudio(dir)) {
|
|
26716
27175
|
return runLocalStudioMode(dir, projectName);
|
|
26717
27176
|
}
|
|
26718
|
-
|
|
27177
|
+
const forceNew = !!args["force-new"];
|
|
27178
|
+
return runEmbeddedMode(dir, startPort, projectName, forceNew);
|
|
26719
27179
|
}
|
|
26720
27180
|
});
|
|
26721
27181
|
}
|
|
@@ -26736,7 +27196,7 @@ import {
|
|
|
26736
27196
|
readFileSync as readFileSync21,
|
|
26737
27197
|
readdirSync as readdirSync10
|
|
26738
27198
|
} from "fs";
|
|
26739
|
-
import { resolve as
|
|
27199
|
+
import { resolve as resolve18, basename as basename4, join as join32, dirname as dirname12 } from "path";
|
|
26740
27200
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
26741
27201
|
import { execFileSync as execFileSync4, spawn as spawn9 } from "child_process";
|
|
26742
27202
|
function probeVideo(filePath) {
|
|
@@ -26806,8 +27266,8 @@ function transcodeToMp4(inputPath, outputPath) {
|
|
|
26806
27266
|
}
|
|
26807
27267
|
function resolveAssetDir(devSegments, builtSegments) {
|
|
26808
27268
|
const base = dirname12(fileURLToPath5(import.meta.url));
|
|
26809
|
-
const devPath =
|
|
26810
|
-
const builtPath =
|
|
27269
|
+
const devPath = resolve18(base, ...devSegments);
|
|
27270
|
+
const builtPath = resolve18(base, ...builtSegments);
|
|
26811
27271
|
return existsSync31(devPath) ? devPath : builtPath;
|
|
26812
27272
|
}
|
|
26813
27273
|
function getStaticTemplateDir(templateId) {
|
|
@@ -26892,7 +27352,7 @@ async function handleVideoFile(videoPath, destDir, interactive) {
|
|
|
26892
27352
|
}
|
|
26893
27353
|
if (shouldTranscode) {
|
|
26894
27354
|
const mp4Name = localVideoName.replace(/\.[^.]+$/, ".mp4");
|
|
26895
|
-
const mp4Path =
|
|
27355
|
+
const mp4Path = resolve18(destDir, mp4Name);
|
|
26896
27356
|
const spin = be();
|
|
26897
27357
|
spin.start("Transcoding to H.264 MP4...");
|
|
26898
27358
|
const ok = await transcodeToMp4(videoPath, mp4Path);
|
|
@@ -26901,10 +27361,10 @@ async function handleVideoFile(videoPath, destDir, interactive) {
|
|
|
26901
27361
|
localVideoName = mp4Name;
|
|
26902
27362
|
} else {
|
|
26903
27363
|
spin.stop(c.warn("Transcode failed \u2014 copying original file"));
|
|
26904
|
-
copyFileSync3(videoPath,
|
|
27364
|
+
copyFileSync3(videoPath, resolve18(destDir, localVideoName));
|
|
26905
27365
|
}
|
|
26906
27366
|
} else {
|
|
26907
|
-
copyFileSync3(videoPath,
|
|
27367
|
+
copyFileSync3(videoPath, resolve18(destDir, localVideoName));
|
|
26908
27368
|
}
|
|
26909
27369
|
} else {
|
|
26910
27370
|
if (interactive) {
|
|
@@ -26914,10 +27374,10 @@ async function handleVideoFile(videoPath, destDir, interactive) {
|
|
|
26914
27374
|
console.log(c.warn("ffmpeg not installed \u2014 cannot transcode. Copying original."));
|
|
26915
27375
|
console.log(c.dim("Install: ") + c.accent("brew install ffmpeg"));
|
|
26916
27376
|
}
|
|
26917
|
-
copyFileSync3(videoPath,
|
|
27377
|
+
copyFileSync3(videoPath, resolve18(destDir, localVideoName));
|
|
26918
27378
|
}
|
|
26919
27379
|
} else {
|
|
26920
|
-
copyFileSync3(videoPath,
|
|
27380
|
+
copyFileSync3(videoPath, resolve18(destDir, localVideoName));
|
|
26921
27381
|
}
|
|
26922
27382
|
return { meta, localVideoName };
|
|
26923
27383
|
}
|
|
@@ -26931,7 +27391,7 @@ async function scaffoldProject(destDir, name, templateId, localVideoName, durati
|
|
|
26931
27391
|
}
|
|
26932
27392
|
patchVideoSrc(destDir, localVideoName, durationSeconds);
|
|
26933
27393
|
writeFileSync11(
|
|
26934
|
-
|
|
27394
|
+
resolve18(destDir, "meta.json"),
|
|
26935
27395
|
JSON.stringify(
|
|
26936
27396
|
{
|
|
26937
27397
|
id: name,
|
|
@@ -26947,7 +27407,7 @@ async function scaffoldProject(destDir, name, templateId, localVideoName, durati
|
|
|
26947
27407
|
if (existsSync31(sharedDir)) {
|
|
26948
27408
|
for (const entry of readdirSync10(sharedDir, { withFileTypes: true })) {
|
|
26949
27409
|
const src = join32(sharedDir, entry.name);
|
|
26950
|
-
const dest =
|
|
27410
|
+
const dest = resolve18(destDir, entry.name);
|
|
26951
27411
|
if (entry.isFile() || entry.isSymbolicLink()) {
|
|
26952
27412
|
copyFileSync3(src, dest);
|
|
26953
27413
|
}
|
|
@@ -27039,7 +27499,7 @@ var init_init = __esm({
|
|
|
27039
27499
|
if (!interactive) {
|
|
27040
27500
|
const templateId2 = templateFlag ?? "blank";
|
|
27041
27501
|
const name2 = args.name ?? "my-video";
|
|
27042
|
-
const destDir2 =
|
|
27502
|
+
const destDir2 = resolve18(name2);
|
|
27043
27503
|
if (existsSync31(destDir2) && readdirSync10(destDir2).length > 0) {
|
|
27044
27504
|
console.error(c.error(`Directory already exists and is not empty: ${name2}`));
|
|
27045
27505
|
process.exit(1);
|
|
@@ -27053,7 +27513,7 @@ var init_init = __esm({
|
|
|
27053
27513
|
process.exit(1);
|
|
27054
27514
|
}
|
|
27055
27515
|
if (videoFlag) {
|
|
27056
|
-
const videoPath =
|
|
27516
|
+
const videoPath = resolve18(videoFlag);
|
|
27057
27517
|
if (!existsSync31(videoPath)) {
|
|
27058
27518
|
console.error(c.error(`Video file not found: ${videoFlag}`));
|
|
27059
27519
|
process.exit(1);
|
|
@@ -27067,13 +27527,13 @@ var init_init = __esm({
|
|
|
27067
27527
|
);
|
|
27068
27528
|
}
|
|
27069
27529
|
if (audioFlag) {
|
|
27070
|
-
const audioPath =
|
|
27530
|
+
const audioPath = resolve18(audioFlag);
|
|
27071
27531
|
if (!existsSync31(audioPath)) {
|
|
27072
27532
|
console.error(c.error(`Audio file not found: ${audioFlag}`));
|
|
27073
27533
|
process.exit(1);
|
|
27074
27534
|
}
|
|
27075
27535
|
sourceFilePath2 = audioPath;
|
|
27076
|
-
copyFileSync3(audioPath,
|
|
27536
|
+
copyFileSync3(audioPath, resolve18(destDir2, basename4(audioPath)));
|
|
27077
27537
|
console.log(`Audio: ${basename4(audioPath)}`);
|
|
27078
27538
|
}
|
|
27079
27539
|
if (sourceFilePath2 && !skipTranscribe) {
|
|
@@ -27113,7 +27573,7 @@ var init_init = __esm({
|
|
|
27113
27573
|
process.exit(1);
|
|
27114
27574
|
}
|
|
27115
27575
|
trackInitTemplate(templateId2);
|
|
27116
|
-
const transcriptFile2 =
|
|
27576
|
+
const transcriptFile2 = resolve18(destDir2, "transcript.json");
|
|
27117
27577
|
if (existsSync31(transcriptFile2)) {
|
|
27118
27578
|
await patchTranscript(destDir2, transcriptFile2);
|
|
27119
27579
|
}
|
|
@@ -27159,7 +27619,7 @@ var init_init = __esm({
|
|
|
27159
27619
|
}
|
|
27160
27620
|
name = nameResult;
|
|
27161
27621
|
}
|
|
27162
|
-
const destDir =
|
|
27622
|
+
const destDir = resolve18(name);
|
|
27163
27623
|
if (existsSync31(destDir) && readdirSync10(destDir).length > 0) {
|
|
27164
27624
|
const overwrite = await Rt({
|
|
27165
27625
|
message: `Directory ${c.accent(name)} already exists and is not empty. Overwrite?`,
|
|
@@ -27174,7 +27634,7 @@ var init_init = __esm({
|
|
|
27174
27634
|
let sourceFilePath;
|
|
27175
27635
|
let videoDuration;
|
|
27176
27636
|
if (videoFlag) {
|
|
27177
|
-
const videoPath =
|
|
27637
|
+
const videoPath = resolve18(videoFlag);
|
|
27178
27638
|
if (!existsSync31(videoPath)) {
|
|
27179
27639
|
R2.error(`File not found: ${videoFlag}`);
|
|
27180
27640
|
Nt("Setup cancelled.");
|
|
@@ -27186,7 +27646,7 @@ var init_init = __esm({
|
|
|
27186
27646
|
localVideoName = result.localVideoName;
|
|
27187
27647
|
videoDuration = result.meta.durationSeconds;
|
|
27188
27648
|
} else if (audioFlag) {
|
|
27189
|
-
const audioPath =
|
|
27649
|
+
const audioPath = resolve18(audioFlag);
|
|
27190
27650
|
if (!existsSync31(audioPath)) {
|
|
27191
27651
|
R2.error(`File not found: ${audioFlag}`);
|
|
27192
27652
|
Nt("Setup cancelled.");
|
|
@@ -27194,7 +27654,7 @@ var init_init = __esm({
|
|
|
27194
27654
|
}
|
|
27195
27655
|
mkdirSync19(destDir, { recursive: true });
|
|
27196
27656
|
sourceFilePath = audioPath;
|
|
27197
|
-
copyFileSync3(audioPath,
|
|
27657
|
+
copyFileSync3(audioPath, resolve18(destDir, basename4(audioPath)));
|
|
27198
27658
|
R2.info(`Audio copied to ${c.accent(basename4(audioPath))}`);
|
|
27199
27659
|
}
|
|
27200
27660
|
if (sourceFilePath) {
|
|
@@ -27279,7 +27739,7 @@ ${c.dim("Use --template blank for offline use.")}`
|
|
|
27279
27739
|
process.exit(1);
|
|
27280
27740
|
}
|
|
27281
27741
|
trackInitTemplate(templateId);
|
|
27282
|
-
const transcriptFile =
|
|
27742
|
+
const transcriptFile = resolve18(destDir, "transcript.json");
|
|
27283
27743
|
if (existsSync31(transcriptFile)) {
|
|
27284
27744
|
await patchTranscript(destDir, transcriptFile);
|
|
27285
27745
|
}
|
|
@@ -27344,11 +27804,11 @@ var init_format = __esm({
|
|
|
27344
27804
|
|
|
27345
27805
|
// src/utils/project.ts
|
|
27346
27806
|
import { existsSync as existsSync32, statSync as statSync11 } from "fs";
|
|
27347
|
-
import { resolve as
|
|
27807
|
+
import { resolve as resolve19, basename as basename5 } from "path";
|
|
27348
27808
|
function resolveProject(dirArg) {
|
|
27349
|
-
const dir =
|
|
27809
|
+
const dir = resolve19(dirArg ?? ".");
|
|
27350
27810
|
const name = basename5(dir);
|
|
27351
|
-
const indexPath =
|
|
27811
|
+
const indexPath = resolve19(dir, "index.html");
|
|
27352
27812
|
if (!existsSync32(dir) || !statSync11(dir).isDirectory()) {
|
|
27353
27813
|
errorBox("Not a directory: " + dir);
|
|
27354
27814
|
process.exit(1);
|
|
@@ -27377,7 +27837,7 @@ __export(play_exports, {
|
|
|
27377
27837
|
examples: () => examples3
|
|
27378
27838
|
});
|
|
27379
27839
|
import { existsSync as existsSync33, readFileSync as readFileSync22 } from "fs";
|
|
27380
|
-
import { resolve as
|
|
27840
|
+
import { resolve as resolve20, dirname as dirname13 } from "path";
|
|
27381
27841
|
function commandDir() {
|
|
27382
27842
|
return dirname13(new URL(import.meta.url).pathname);
|
|
27383
27843
|
}
|
|
@@ -27385,10 +27845,10 @@ function resolveRuntimePath2() {
|
|
|
27385
27845
|
const d = commandDir();
|
|
27386
27846
|
const candidates = [
|
|
27387
27847
|
// Bundled with CLI dist
|
|
27388
|
-
|
|
27389
|
-
|
|
27848
|
+
resolve20(d, "hyperframe-runtime.js"),
|
|
27849
|
+
resolve20(d, "..", "hyperframe-runtime.js"),
|
|
27390
27850
|
// Monorepo dev: commands/ → src/ → cli/ → packages/ then into core/dist/
|
|
27391
|
-
|
|
27851
|
+
resolve20(d, "..", "..", "..", "core", "dist", "hyperframe.runtime.iife.js")
|
|
27392
27852
|
];
|
|
27393
27853
|
for (const p of candidates) {
|
|
27394
27854
|
if (existsSync33(p)) return p;
|
|
@@ -27399,10 +27859,10 @@ function resolvePlayerPath() {
|
|
|
27399
27859
|
const d = commandDir();
|
|
27400
27860
|
const candidates = [
|
|
27401
27861
|
// Monorepo dev: commands/ → src/ → cli/ → packages/ then into player/dist/
|
|
27402
|
-
|
|
27862
|
+
resolve20(d, "..", "..", "..", "player", "dist", "hyperframes-player.global.js"),
|
|
27403
27863
|
// Bundled with CLI dist
|
|
27404
|
-
|
|
27405
|
-
|
|
27864
|
+
resolve20(d, "hyperframes-player.global.js"),
|
|
27865
|
+
resolve20(d, "..", "hyperframes-player.global.js")
|
|
27406
27866
|
];
|
|
27407
27867
|
for (const p of candidates) {
|
|
27408
27868
|
if (existsSync33(p)) return p;
|
|
@@ -27505,7 +27965,7 @@ var init_play = __esm({
|
|
|
27505
27965
|
});
|
|
27506
27966
|
app.get("/composition/*", async (ctx) => {
|
|
27507
27967
|
const reqPath = ctx.req.path.replace("/composition/", "");
|
|
27508
|
-
const filePath =
|
|
27968
|
+
const filePath = resolve20(project.dir, reqPath);
|
|
27509
27969
|
if (!filePath.startsWith(project.dir)) return ctx.text("Forbidden", 403);
|
|
27510
27970
|
if (!existsSync33(filePath)) return ctx.text("Not found", 404);
|
|
27511
27971
|
const content = readFileSync22(filePath, "utf-8");
|
|
@@ -27657,7 +28117,7 @@ __export(render_exports, {
|
|
|
27657
28117
|
});
|
|
27658
28118
|
import { mkdirSync as mkdirSync20, readFileSync as readFileSync23, statSync as statSync12, writeFileSync as writeFileSync12, rmSync as rmSync8 } from "fs";
|
|
27659
28119
|
import { cpus as cpus3, freemem as freemem3, tmpdir as tmpdir3 } from "os";
|
|
27660
|
-
import { resolve as
|
|
28120
|
+
import { resolve as resolve21, dirname as dirname14, join as join33, basename as basename6 } from "path";
|
|
27661
28121
|
import { execFileSync as execFileSync5, spawn as spawn10 } from "child_process";
|
|
27662
28122
|
function defaultWorkerCount() {
|
|
27663
28123
|
return Math.max(1, Math.min(Math.floor(CPU_CORE_COUNT * 3 / 4), 8));
|
|
@@ -27666,8 +28126,8 @@ function dockerImageTag(version) {
|
|
|
27666
28126
|
return `${DOCKER_IMAGE_PREFIX}:${version}`;
|
|
27667
28127
|
}
|
|
27668
28128
|
function resolveDockerfilePath() {
|
|
27669
|
-
const builtPath =
|
|
27670
|
-
const devPath =
|
|
28129
|
+
const builtPath = resolve21(__dirname, "docker", "Dockerfile.render");
|
|
28130
|
+
const devPath = resolve21(__dirname, "..", "src", "docker", "Dockerfile.render");
|
|
27671
28131
|
for (const p of [builtPath, devPath]) {
|
|
27672
28132
|
try {
|
|
27673
28133
|
statSync12(p);
|
|
@@ -27751,9 +28211,9 @@ async function renderDocker(projectDir, outputPath, options) {
|
|
|
27751
28211
|
// GPU encoding requires host GPU passthrough
|
|
27752
28212
|
...options.gpu ? ["--gpus", "all"] : [],
|
|
27753
28213
|
"-v",
|
|
27754
|
-
`${
|
|
28214
|
+
`${resolve21(projectDir)}:/project:ro`,
|
|
27755
28215
|
"-v",
|
|
27756
|
-
`${
|
|
28216
|
+
`${resolve21(outputDir)}:/output`,
|
|
27757
28217
|
imageTag,
|
|
27758
28218
|
"/project",
|
|
27759
28219
|
"--output",
|
|
@@ -27962,6 +28422,10 @@ var init_render2 = __esm({
|
|
|
27962
28422
|
type: "boolean",
|
|
27963
28423
|
description: "Fail render on lint errors AND warnings",
|
|
27964
28424
|
default: false
|
|
28425
|
+
},
|
|
28426
|
+
"max-concurrent-renders": {
|
|
28427
|
+
type: "string",
|
|
28428
|
+
description: "Max concurrent renders when using the producer server (1-10). Default: 2."
|
|
27965
28429
|
}
|
|
27966
28430
|
},
|
|
27967
28431
|
async run({ args }) {
|
|
@@ -27993,12 +28457,23 @@ var init_render2 = __esm({
|
|
|
27993
28457
|
}
|
|
27994
28458
|
workers = parsed;
|
|
27995
28459
|
}
|
|
27996
|
-
|
|
28460
|
+
if (args["max-concurrent-renders"] != null) {
|
|
28461
|
+
const parsed = parseInt(args["max-concurrent-renders"], 10);
|
|
28462
|
+
if (isNaN(parsed) || parsed < 1 || parsed > 10) {
|
|
28463
|
+
errorBox(
|
|
28464
|
+
"Invalid max-concurrent-renders",
|
|
28465
|
+
`Got "${args["max-concurrent-renders"]}". Must be a number between 1 and 10.`
|
|
28466
|
+
);
|
|
28467
|
+
process.exit(1);
|
|
28468
|
+
}
|
|
28469
|
+
process.env.PRODUCER_MAX_CONCURRENT_RENDERS = String(parsed);
|
|
28470
|
+
}
|
|
28471
|
+
const rendersDir = resolve21("renders");
|
|
27997
28472
|
const ext = FORMAT_EXT[format] ?? ".mp4";
|
|
27998
28473
|
const now = /* @__PURE__ */ new Date();
|
|
27999
28474
|
const datePart = now.toISOString().slice(0, 10);
|
|
28000
28475
|
const timePart = now.toTimeString().slice(0, 8).replace(/:/g, "-");
|
|
28001
|
-
const outputPath = args.output ?
|
|
28476
|
+
const outputPath = args.output ? resolve21(args.output) : join33(rendersDir, `${project.name}_${datePart}_${timePart}${ext}`);
|
|
28002
28477
|
mkdirSync20(dirname14(outputPath), { recursive: true });
|
|
28003
28478
|
const useDocker = args.docker ?? false;
|
|
28004
28479
|
const useGpu = args.gpu ?? false;
|
|
@@ -28412,7 +28887,7 @@ __export(compositions_exports, {
|
|
|
28412
28887
|
examples: () => examples7
|
|
28413
28888
|
});
|
|
28414
28889
|
import { existsSync as existsSync34, readFileSync as readFileSync25 } from "fs";
|
|
28415
|
-
import { resolve as
|
|
28890
|
+
import { resolve as resolve22, dirname as dirname15 } from "path";
|
|
28416
28891
|
function parseCompositions(html, baseDir) {
|
|
28417
28892
|
const parser = new DOMParser();
|
|
28418
28893
|
const doc = parser.parseFromString(html, "text/html");
|
|
@@ -28424,7 +28899,7 @@ function parseCompositions(html, baseDir) {
|
|
|
28424
28899
|
const height = parseInt(div.getAttribute("data-height") ?? "1080", 10);
|
|
28425
28900
|
const compositionSrc = div.getAttribute("data-composition-src");
|
|
28426
28901
|
if (compositionSrc) {
|
|
28427
|
-
const subPath =
|
|
28902
|
+
const subPath = resolve22(baseDir, compositionSrc);
|
|
28428
28903
|
if (existsSync34(subPath)) {
|
|
28429
28904
|
const subHtml = readFileSync25(subPath, "utf-8");
|
|
28430
28905
|
const subInfo = parseSubComposition(subHtml, id, width, height);
|
|
@@ -28559,7 +29034,7 @@ __export(benchmark_exports, {
|
|
|
28559
29034
|
examples: () => examples8
|
|
28560
29035
|
});
|
|
28561
29036
|
import { existsSync as existsSync35, statSync as statSync14 } from "fs";
|
|
28562
|
-
import { resolve as
|
|
29037
|
+
import { resolve as resolve23, join as join35 } from "path";
|
|
28563
29038
|
var examples8, DEFAULT_CONFIGS, benchmark_default;
|
|
28564
29039
|
var init_benchmark = __esm({
|
|
28565
29040
|
"src/commands/benchmark.ts"() {
|
|
@@ -28601,7 +29076,7 @@ var init_benchmark = __esm({
|
|
|
28601
29076
|
process.exit(1);
|
|
28602
29077
|
}
|
|
28603
29078
|
const jsonOutput = args.json ?? false;
|
|
28604
|
-
const benchDir =
|
|
29079
|
+
const benchDir = resolve23("renders", ".benchmark");
|
|
28605
29080
|
let producer = null;
|
|
28606
29081
|
try {
|
|
28607
29082
|
producer = await loadProducer();
|
|
@@ -28865,7 +29340,7 @@ __export(transcribe_exports2, {
|
|
|
28865
29340
|
examples: () => examples10
|
|
28866
29341
|
});
|
|
28867
29342
|
import { existsSync as existsSync36, writeFileSync as writeFileSync13 } from "fs";
|
|
28868
|
-
import { resolve as
|
|
29343
|
+
import { resolve as resolve24, join as join36, extname as extname7 } from "path";
|
|
28869
29344
|
async function importTranscript(inputPath, dir, json) {
|
|
28870
29345
|
const { loadTranscript: loadTranscript2, patchCaptionHtml: patchCaptionHtml2 } = await Promise.resolve().then(() => (init_normalize(), normalize_exports));
|
|
28871
29346
|
const { words, format } = loadTranscript2(inputPath);
|
|
@@ -28989,12 +29464,12 @@ var init_transcribe2 = __esm({
|
|
|
28989
29464
|
}
|
|
28990
29465
|
},
|
|
28991
29466
|
async run({ args }) {
|
|
28992
|
-
const inputPath =
|
|
29467
|
+
const inputPath = resolve24(args.input);
|
|
28993
29468
|
if (!existsSync36(inputPath)) {
|
|
28994
29469
|
console.error(c.error(`File not found: ${args.input}`));
|
|
28995
29470
|
process.exit(1);
|
|
28996
29471
|
}
|
|
28997
|
-
const dir =
|
|
29472
|
+
const dir = resolve24(args.dir ?? ".");
|
|
28998
29473
|
const ext = extname7(inputPath).toLowerCase();
|
|
28999
29474
|
const isImport = ext === ".json" || ext === ".srt" || ext === ".vtt";
|
|
29000
29475
|
if (isImport) {
|
|
@@ -29217,7 +29692,7 @@ __export(tts_exports, {
|
|
|
29217
29692
|
examples: () => examples11
|
|
29218
29693
|
});
|
|
29219
29694
|
import { existsSync as existsSync39, readFileSync as readFileSync26 } from "fs";
|
|
29220
|
-
import { resolve as
|
|
29695
|
+
import { resolve as resolve25, extname as extname8 } from "path";
|
|
29221
29696
|
function listVoices(json) {
|
|
29222
29697
|
if (json) {
|
|
29223
29698
|
console.log(JSON.stringify(BUNDLED_VOICES));
|
|
@@ -29305,7 +29780,7 @@ var init_tts = __esm({
|
|
|
29305
29780
|
process.exit(1);
|
|
29306
29781
|
}
|
|
29307
29782
|
let text;
|
|
29308
|
-
const maybeFile =
|
|
29783
|
+
const maybeFile = resolve25(args.input);
|
|
29309
29784
|
if (existsSync39(maybeFile) && extname8(maybeFile).toLowerCase() === ".txt") {
|
|
29310
29785
|
text = readFileSync26(maybeFile, "utf-8").trim();
|
|
29311
29786
|
if (!text) {
|
|
@@ -29319,7 +29794,7 @@ var init_tts = __esm({
|
|
|
29319
29794
|
console.error(c.error("No text provided."));
|
|
29320
29795
|
process.exit(1);
|
|
29321
29796
|
}
|
|
29322
|
-
const output =
|
|
29797
|
+
const output = resolve25(args.output ?? "speech.wav");
|
|
29323
29798
|
const voice = args.voice ?? DEFAULT_VOICE;
|
|
29324
29799
|
const speed = args.speed ? parseFloat(args.speed) : 1;
|
|
29325
29800
|
if (isNaN(speed) || speed <= 0 || speed > 3) {
|
|
@@ -29373,13 +29848,13 @@ __export(docs_exports, {
|
|
|
29373
29848
|
examples: () => examples12
|
|
29374
29849
|
});
|
|
29375
29850
|
import { readFileSync as readFileSync27, existsSync as existsSync40 } from "fs";
|
|
29376
|
-
import { resolve as
|
|
29851
|
+
import { resolve as resolve26, dirname as dirname17, join as join39 } from "path";
|
|
29377
29852
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
29378
29853
|
function docsDir() {
|
|
29379
29854
|
const thisFile = fileURLToPath6(import.meta.url);
|
|
29380
29855
|
const dir = dirname17(thisFile);
|
|
29381
|
-
const devPath =
|
|
29382
|
-
const builtPath =
|
|
29856
|
+
const devPath = resolve26(dir, "..", "docs");
|
|
29857
|
+
const builtPath = resolve26(dir, "docs");
|
|
29383
29858
|
return existsSync40(devPath) ? devPath : builtPath;
|
|
29384
29859
|
}
|
|
29385
29860
|
function formatInlineCode(line) {
|
|
@@ -29897,13 +30372,13 @@ __export(validate_exports, {
|
|
|
29897
30372
|
default: () => validate_default
|
|
29898
30373
|
});
|
|
29899
30374
|
import { existsSync as existsSync41, readFileSync as readFileSync28 } from "fs";
|
|
29900
|
-
import { resolve as
|
|
30375
|
+
import { resolve as resolve27, join as join40, dirname as dirname18 } from "path";
|
|
29901
30376
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
29902
30377
|
async function validateInBrowser(projectDir, opts) {
|
|
29903
30378
|
const { bundleToSingleHtml: bundleToSingleHtml2 } = await Promise.resolve().then(() => (init_compiler(), compiler_exports));
|
|
29904
30379
|
const { ensureBrowser: ensureBrowser2 } = await Promise.resolve().then(() => (init_manager2(), manager_exports2));
|
|
29905
30380
|
let html = await bundleToSingleHtml2(projectDir);
|
|
29906
|
-
const runtimePath =
|
|
30381
|
+
const runtimePath = resolve27(
|
|
29907
30382
|
__dirname2,
|
|
29908
30383
|
"..",
|
|
29909
30384
|
"..",
|
|
@@ -29916,7 +30391,7 @@ async function validateInBrowser(projectDir, opts) {
|
|
|
29916
30391
|
const runtimeSource = readFileSync28(runtimePath, "utf-8");
|
|
29917
30392
|
html = html.replace(
|
|
29918
30393
|
/<script[^>]*data-hyperframes-preview-runtime[^>]*src="[^"]*"[^>]*><\/script>/,
|
|
29919
|
-
`<script data-hyperframes-preview-runtime="1">${runtimeSource}</script>`
|
|
30394
|
+
() => `<script data-hyperframes-preview-runtime="1">${runtimeSource}</script>`
|
|
29920
30395
|
);
|
|
29921
30396
|
}
|
|
29922
30397
|
const { createServer } = await import("http");
|
|
@@ -30246,7 +30721,8 @@ var init_help = __esm({
|
|
|
30246
30721
|
[
|
|
30247
30722
|
"transcribe",
|
|
30248
30723
|
"Transcribe audio/video to word-level timestamps, or import an existing transcript"
|
|
30249
|
-
]
|
|
30724
|
+
],
|
|
30725
|
+
["tts", "Generate speech audio from text using a local AI model (Kokoro-82M)"]
|
|
30250
30726
|
]
|
|
30251
30727
|
},
|
|
30252
30728
|
{
|