@hyperframes/producer 0.9.19 → 0.9.21
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/distributed.js +322 -129
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +65 -65
- package/dist/index.js +349 -174
- package/dist/public-server.js +244 -101
- package/dist/renderRequest.d.ts +1 -0
- package/dist/services/distributed/plan.d.ts +7 -5
- package/dist/services/fileServer.d.ts +3 -3
- package/dist/services/render/stages/captureStreamingStage.d.ts +11 -2
- package/dist/services/renderOrchestrator.d.ts +3 -2
- package/dist/utils/paths.d.ts +2 -0
- package/package.json +7 -7
package/dist/public-server.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/server.ts
|
|
4
4
|
import {
|
|
5
|
-
existsSync as
|
|
5
|
+
existsSync as existsSync24,
|
|
6
6
|
mkdirSync as mkdirSync13,
|
|
7
7
|
statSync as statSync11,
|
|
8
8
|
mkdtempSync as mkdtempSync4,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
// src/services/renderOrchestrator.ts
|
|
27
27
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
28
28
|
import {
|
|
29
|
-
existsSync as
|
|
29
|
+
existsSync as existsSync21,
|
|
30
30
|
mkdirSync as mkdirSync12,
|
|
31
31
|
readdirSync as readdirSync6,
|
|
32
32
|
rmSync as rmSync12,
|
|
@@ -427,6 +427,68 @@ ${seededRandomBlock}
|
|
|
427
427
|
}
|
|
428
428
|
};
|
|
429
429
|
|
|
430
|
+
// Timers set once rendering has started run on film time: each seek fires every timer due by then, in
|
|
431
|
+
// due order. Until a seek after the page reports ready, timers keep the wall clock: setup code waits on
|
|
432
|
+
// them while the renderer may already be seeking.
|
|
433
|
+
var rendering = false;
|
|
434
|
+
var timerQueue = [];
|
|
435
|
+
var nextTimerId = 1000000000;
|
|
436
|
+
var firingDepth = 0;
|
|
437
|
+
function addTimer(callback, delay, args, repeats) {
|
|
438
|
+
// A timer set from a firing timer waits at least 1 ms, so a zero-delay chain cannot stall a seek.
|
|
439
|
+
var ms = Math.max(firingDepth > 0 ? 1 : 0, Number(delay) || 0);
|
|
440
|
+
var entry = { id: nextTimerId++, callback: callback, args: args, due: virtualNowMs + ms, every: repeats ? Math.max(1, ms) : 0, cancelled: false };
|
|
441
|
+
timerQueue.push(entry);
|
|
442
|
+
return entry.id;
|
|
443
|
+
}
|
|
444
|
+
function cancelTimer(id) {
|
|
445
|
+
for (var i = 0; i < timerQueue.length; i++) {
|
|
446
|
+
if (timerQueue[i].id === id) {
|
|
447
|
+
timerQueue[i].cancelled = true;
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
function fireDueTimers(untilMs) {
|
|
454
|
+
// Frame times are fractional, so a timer due on a frame lands a rounding error past it; it still fires on it.
|
|
455
|
+
untilMs += 0.000001;
|
|
456
|
+
while (true) {
|
|
457
|
+
var next = null;
|
|
458
|
+
for (var i = 0; i < timerQueue.length; i++) {
|
|
459
|
+
var candidate = timerQueue[i];
|
|
460
|
+
if (!candidate.cancelled && candidate.due <= untilMs && (next === null || candidate.due < next.due)) next = candidate;
|
|
461
|
+
}
|
|
462
|
+
if (next === null) break;
|
|
463
|
+
var dueMs = next.due;
|
|
464
|
+
if (next.every) next.due += next.every;
|
|
465
|
+
else next.cancelled = true;
|
|
466
|
+
virtualNowMs = dueMs;
|
|
467
|
+
firingDepth++;
|
|
468
|
+
try {
|
|
469
|
+
next.callback.apply(window, next.args);
|
|
470
|
+
} catch (err) {
|
|
471
|
+
originalSetTimeout(function() { throw err; }, 0);
|
|
472
|
+
}
|
|
473
|
+
firingDepth--;
|
|
474
|
+
}
|
|
475
|
+
timerQueue = timerQueue.filter(function(entry) { return !entry.cancelled; });
|
|
476
|
+
}
|
|
477
|
+
window.setTimeout = function(callback, delay) {
|
|
478
|
+
if (!rendering || typeof callback !== "function") return originalSetTimeout.apply(null, arguments);
|
|
479
|
+
return addTimer(callback, delay, Array.prototype.slice.call(arguments, 2), false);
|
|
480
|
+
};
|
|
481
|
+
window.setInterval = function(callback, delay) {
|
|
482
|
+
if (!rendering || typeof callback !== "function") return originalSetInterval.apply(null, arguments);
|
|
483
|
+
return addTimer(callback, delay, Array.prototype.slice.call(arguments, 2), true);
|
|
484
|
+
};
|
|
485
|
+
window.clearTimeout = function(id) {
|
|
486
|
+
if (!cancelTimer(id)) originalClearTimeout(id);
|
|
487
|
+
};
|
|
488
|
+
window.clearInterval = function(id) {
|
|
489
|
+
if (!cancelTimer(id)) originalClearInterval(id);
|
|
490
|
+
};
|
|
491
|
+
|
|
430
492
|
window.__HF_VIRTUAL_TIME__ = {
|
|
431
493
|
originalSetTimeout: originalSetTimeout,
|
|
432
494
|
originalClearTimeout: originalClearTimeout,
|
|
@@ -436,8 +498,10 @@ ${seededRandomBlock}
|
|
|
436
498
|
originalCancelAnimationFrame: originalCancelAnimationFrame,
|
|
437
499
|
seekToTime: function(nextTimeMs) {
|
|
438
500
|
var safeTimeMs = Math.max(0, Number(nextTimeMs) || 0);
|
|
501
|
+
if (window.__renderReady === true) rendering = true;
|
|
502
|
+
${seekToTimeReseedCall}fireDueTimers(safeTimeMs);
|
|
439
503
|
virtualNowMs = safeTimeMs;
|
|
440
|
-
|
|
504
|
+
flushAnimationFrame();
|
|
441
505
|
return virtualNowMs;
|
|
442
506
|
},
|
|
443
507
|
getTime: function() {
|
|
@@ -932,7 +996,7 @@ function resolveDeliveryEncoderOptions(config) {
|
|
|
932
996
|
import {
|
|
933
997
|
copyFileSync,
|
|
934
998
|
cpSync,
|
|
935
|
-
existsSync as
|
|
999
|
+
existsSync as existsSync4,
|
|
936
1000
|
mkdirSync,
|
|
937
1001
|
rmSync,
|
|
938
1002
|
symlinkSync,
|
|
@@ -952,7 +1016,9 @@ import {
|
|
|
952
1016
|
relative as nodeRelative,
|
|
953
1017
|
isAbsolute as nodeIsAbsolute
|
|
954
1018
|
} from "node:path";
|
|
1019
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
955
1020
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1021
|
+
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
956
1022
|
var DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? // fileURLToPath (not URL.pathname): on Windows .pathname is "/D:/..." which
|
|
957
1023
|
// resolves to a bogus renders dir.
|
|
958
1024
|
nodeResolve(fileURLToPath2(import.meta.url), "../../..", "renders");
|
|
@@ -990,6 +1056,13 @@ function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS
|
|
|
990
1056
|
const absoluteOutputPath = nodeResolve(resolvedOutputPath);
|
|
991
1057
|
return { absoluteProjectDir, absoluteOutputPath };
|
|
992
1058
|
}
|
|
1059
|
+
function resolveCompositionSrc(projectDir, src) {
|
|
1060
|
+
const rooted = (value) => nodeResolve(projectDir, value.replace(/^(?:\.?[\\/])+/, ""));
|
|
1061
|
+
const decoded = rooted(decodedUrlPath(src));
|
|
1062
|
+
if (!isPathInside2(decoded, projectDir)) return null;
|
|
1063
|
+
const raw = rooted(src);
|
|
1064
|
+
return !existsSync3(decoded) && isPathInside2(raw, projectDir) && existsSync3(raw) ? raw : decoded;
|
|
1065
|
+
}
|
|
993
1066
|
|
|
994
1067
|
// src/services/render/shared.ts
|
|
995
1068
|
var BROWSER_MEDIA_EPSILON = 1e-4;
|
|
@@ -1193,7 +1266,7 @@ var materializePathModule = {
|
|
|
1193
1266
|
isAbsolute
|
|
1194
1267
|
};
|
|
1195
1268
|
var materializeFileSystem = {
|
|
1196
|
-
existsSync:
|
|
1269
|
+
existsSync: existsSync4,
|
|
1197
1270
|
mkdirSync,
|
|
1198
1271
|
symlinkSync,
|
|
1199
1272
|
cpSync,
|
|
@@ -1495,7 +1568,7 @@ var RenderExecutionContext = class {
|
|
|
1495
1568
|
};
|
|
1496
1569
|
|
|
1497
1570
|
// src/services/render/artifactTransaction.ts
|
|
1498
|
-
import { closeSync as closeSync2, existsSync as
|
|
1571
|
+
import { closeSync as closeSync2, existsSync as existsSync6, fstatSync as fstatSync2, openSync as openSync2, readSync, readdirSync as readdirSync2, rmSync as rmSync3 } from "node:fs";
|
|
1499
1572
|
import { basename as basename3, dirname as dirname4, extname as extname2, join as join5, resolve as resolve4 } from "node:path";
|
|
1500
1573
|
import { HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST, renameWithRetry } from "@hyperframes/engine";
|
|
1501
1574
|
|
|
@@ -1508,7 +1581,7 @@ import {
|
|
|
1508
1581
|
|
|
1509
1582
|
// src/services/render/renderDirOwner.ts
|
|
1510
1583
|
import {
|
|
1511
|
-
existsSync as
|
|
1584
|
+
existsSync as existsSync5,
|
|
1512
1585
|
lstatSync,
|
|
1513
1586
|
mkdtempSync,
|
|
1514
1587
|
readFileSync as readFileSync2,
|
|
@@ -1583,7 +1656,7 @@ function newestWriteMs(path) {
|
|
|
1583
1656
|
}
|
|
1584
1657
|
function isAbandoned(dir, ownerlessIdleMs, now) {
|
|
1585
1658
|
try {
|
|
1586
|
-
if (!lstatSync(dir).isDirectory() ||
|
|
1659
|
+
if (!lstatSync(dir).isDirectory() || existsSync5(join4(dir, TRANSACTION_BACKUP))) return false;
|
|
1587
1660
|
const owner = ownerState(dir);
|
|
1588
1661
|
if (owner === "gone") return true;
|
|
1589
1662
|
return owner === "none" && ownerlessIdleMs !== void 0 && now - newestWriteMs(dir) >= ownerlessIdleMs;
|
|
@@ -1615,7 +1688,7 @@ function createRenderWorkDir(prefix, outputDir) {
|
|
|
1615
1688
|
|
|
1616
1689
|
// src/services/render/artifactTransaction.ts
|
|
1617
1690
|
var defaultFileSystem = {
|
|
1618
|
-
existsSync:
|
|
1691
|
+
existsSync: existsSync6,
|
|
1619
1692
|
renameSync: (source, destination) => renameWithRetry(source, destination),
|
|
1620
1693
|
rmSync: rmSync3
|
|
1621
1694
|
};
|
|
@@ -1769,7 +1842,7 @@ var ArtifactTransaction = class {
|
|
|
1769
1842
|
async assertDirectoryExpectation(expected, files) {
|
|
1770
1843
|
for (const required of expected.requiredFiles ?? []) {
|
|
1771
1844
|
const path = join5(this.stagingPath, required);
|
|
1772
|
-
if (!
|
|
1845
|
+
if (!existsSync6(path)) {
|
|
1773
1846
|
throw new Error(`Render artifact directory is missing ${required}: ${this.stagingPath}`);
|
|
1774
1847
|
}
|
|
1775
1848
|
assertReadableNonEmptyFile(path);
|
|
@@ -2018,7 +2091,7 @@ function replanAfterFailure(plan, failure) {
|
|
|
2018
2091
|
}
|
|
2019
2092
|
|
|
2020
2093
|
// src/services/render/stages/captureSegmentedStage.ts
|
|
2021
|
-
import { existsSync as
|
|
2094
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, rmSync as rmSync6, statSync as statSync3 } from "node:fs";
|
|
2022
2095
|
import { join as join10 } from "node:path";
|
|
2023
2096
|
import {
|
|
2024
2097
|
captureFrameToBuffer as captureFrameToBuffer2,
|
|
@@ -2315,6 +2388,19 @@ function aggregateBeginFrameReuse(perfs) {
|
|
|
2315
2388
|
if (noDamageFrames + hasDamageFrames === 0) return void 0;
|
|
2316
2389
|
return { noDamageFrames, hasDamageFrames };
|
|
2317
2390
|
}
|
|
2391
|
+
function aggregateMotionBlur(perfs) {
|
|
2392
|
+
const ran = perfs.flatMap((p) => p.motionBlur ?? []).filter((m) => m.tier || m.failedFrames > 0);
|
|
2393
|
+
const first = ran[0];
|
|
2394
|
+
if (!first) return void 0;
|
|
2395
|
+
return {
|
|
2396
|
+
tier: first.tier ?? ran.find((m) => m.tier)?.tier ?? null,
|
|
2397
|
+
reasonCode: first.reasonCode,
|
|
2398
|
+
movers: first.movers,
|
|
2399
|
+
failedFrames: ran.reduce((sum, m) => sum + m.failedFrames, 0),
|
|
2400
|
+
readEvents: ran.some((m) => m.readEvents),
|
|
2401
|
+
fingerprint: ran.find((m) => m.fingerprint)?.fingerprint ?? null
|
|
2402
|
+
};
|
|
2403
|
+
}
|
|
2318
2404
|
function buildRenderPerfSummary(input) {
|
|
2319
2405
|
return {
|
|
2320
2406
|
renderId: input.job.id,
|
|
@@ -2365,6 +2451,7 @@ function buildRenderPerfSummary(input) {
|
|
|
2365
2451
|
staticDedup: aggregateDedup(input.dedupPerfs),
|
|
2366
2452
|
chromeMemory: aggregateChromeMemory(input.dedupPerfs),
|
|
2367
2453
|
beginFrameReuse: aggregateBeginFrameReuse(input.dedupPerfs),
|
|
2454
|
+
motionBlur: aggregateMotionBlur(input.dedupPerfs),
|
|
2368
2455
|
drawElement: aggregateDrawElement(
|
|
2369
2456
|
input.dedupPerfs,
|
|
2370
2457
|
input.drawElement ?? { selfVerifyFallback: false }
|
|
@@ -3839,6 +3926,21 @@ function raceAgainstStall(promise, deadlineMs, input, signal) {
|
|
|
3839
3926
|
);
|
|
3840
3927
|
});
|
|
3841
3928
|
}
|
|
3929
|
+
function createSessionInitBudget(input) {
|
|
3930
|
+
const initStallMs = Math.max(SESSION_INIT_STALL_MS, resolveCaptureStallTimeoutMs());
|
|
3931
|
+
const setup = createStallClock();
|
|
3932
|
+
return (work, stalled) => raceAgainstStall(
|
|
3933
|
+
work,
|
|
3934
|
+
initStallMs - setup.idle(0),
|
|
3935
|
+
{
|
|
3936
|
+
session: stalled,
|
|
3937
|
+
frameIndex: input.frameIndex,
|
|
3938
|
+
totalFrames: input.totalFrames,
|
|
3939
|
+
stallTimeoutMs: initStallMs
|
|
3940
|
+
},
|
|
3941
|
+
input.abortSignal
|
|
3942
|
+
);
|
|
3943
|
+
}
|
|
3842
3944
|
function createDrainFrameGuard(args) {
|
|
3843
3945
|
const { log, stats, frameTime } = args;
|
|
3844
3946
|
const verifyMinDb = resolveDeVerifyMinDb();
|
|
@@ -4311,14 +4413,11 @@ async function runCaptureStreamingStage(input) {
|
|
|
4311
4413
|
let resumes = 0;
|
|
4312
4414
|
for (; ; ) {
|
|
4313
4415
|
const videoInjector = createRenderVideoFrameInjector();
|
|
4314
|
-
const
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
work,
|
|
4318
|
-
initStallMs - setup.idle(0),
|
|
4319
|
-
{ session: stalled, frameIndex: resume.from, totalFrames, stallTimeoutMs: initStallMs },
|
|
4416
|
+
const withinInitBudget = createSessionInitBudget({
|
|
4417
|
+
frameIndex: resume.from,
|
|
4418
|
+
totalFrames,
|
|
4320
4419
|
abortSignal
|
|
4321
|
-
);
|
|
4420
|
+
});
|
|
4322
4421
|
const openSession = async () => {
|
|
4323
4422
|
const opening = createCaptureSession(
|
|
4324
4423
|
fileServer.url,
|
|
@@ -4596,7 +4695,7 @@ async function captureSegmentToEncoder(ctx, segment) {
|
|
|
4596
4695
|
path: segmentPath,
|
|
4597
4696
|
// Recorded so resume can prove the file on disk is the one that
|
|
4598
4697
|
// finished; absent only in unit tests, whose fake encoder writes none.
|
|
4599
|
-
bytes:
|
|
4698
|
+
bytes: existsSync7(segmentPath) ? statSync3(segmentPath).size : 0
|
|
4600
4699
|
});
|
|
4601
4700
|
return encodeResult;
|
|
4602
4701
|
} finally {
|
|
@@ -4626,14 +4725,29 @@ function defaultSessionFactory(input) {
|
|
|
4626
4725
|
});
|
|
4627
4726
|
probeSession = null;
|
|
4628
4727
|
}
|
|
4629
|
-
const
|
|
4728
|
+
const withinInitBudget = createSessionInitBudget({
|
|
4729
|
+
frameIndex: input.job.framesRendered ?? 0,
|
|
4730
|
+
totalFrames: input.totalFrames,
|
|
4731
|
+
abortSignal: input.abortSignal
|
|
4732
|
+
});
|
|
4733
|
+
const openSession = async () => {
|
|
4734
|
+
const opening = openSegmentedSession(input, injector);
|
|
4735
|
+
try {
|
|
4736
|
+
return await withinInitBudget(opening, OPENING_CAPTURE_SESSION);
|
|
4737
|
+
} catch (error) {
|
|
4738
|
+
void opening.then(closeCaptureSession2, () => {
|
|
4739
|
+
});
|
|
4740
|
+
throw error;
|
|
4741
|
+
}
|
|
4742
|
+
};
|
|
4743
|
+
const session = probeSession ?? await openSession();
|
|
4630
4744
|
if (probeSession) {
|
|
4631
4745
|
openSegmentedSessionReuse(input, probeSession, injector);
|
|
4632
4746
|
probeSession = null;
|
|
4633
4747
|
}
|
|
4634
4748
|
try {
|
|
4635
|
-
if (!session.isInitialized) await initializeSession2(session);
|
|
4636
|
-
await completeDeferredDrawElementInit2(session);
|
|
4749
|
+
if (!session.isInitialized) await withinInitBudget(initializeSession2(session), session);
|
|
4750
|
+
await withinInitBudget(completeDeferredDrawElementInit2(session), session);
|
|
4637
4751
|
} catch (error) {
|
|
4638
4752
|
await closeCaptureSession2(session).catch(() => {
|
|
4639
4753
|
});
|
|
@@ -5565,7 +5679,7 @@ function trackSegmentFramePeak(frames, rootDir, onPeak) {
|
|
|
5565
5679
|
|
|
5566
5680
|
// src/services/render/segmentManifest.ts
|
|
5567
5681
|
import { createHash as createHash2 } from "node:crypto";
|
|
5568
|
-
import { existsSync as
|
|
5682
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5569
5683
|
import { join as join15 } from "node:path";
|
|
5570
5684
|
import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
|
|
5571
5685
|
var MANIFEST_FILENAME = "segments.json";
|
|
@@ -5613,7 +5727,7 @@ function isManifest(value) {
|
|
|
5613
5727
|
}
|
|
5614
5728
|
function readSegmentManifest(segmentDir) {
|
|
5615
5729
|
const path = join15(segmentDir, MANIFEST_FILENAME);
|
|
5616
|
-
if (!
|
|
5730
|
+
if (!existsSync8(path)) return null;
|
|
5617
5731
|
try {
|
|
5618
5732
|
const parsed = JSON.parse(readFileSync4(path, "utf-8"));
|
|
5619
5733
|
return isManifest(parsed) ? parsed : null;
|
|
@@ -5642,7 +5756,7 @@ async function validateCompletedSegments(manifest, expectedHash, probeFrames) {
|
|
|
5642
5756
|
const ok = /* @__PURE__ */ new Set();
|
|
5643
5757
|
if (manifest.planHash !== expectedHash) return ok;
|
|
5644
5758
|
for (const entry of manifest.completed) {
|
|
5645
|
-
if (!
|
|
5759
|
+
if (!existsSync8(entry.path)) continue;
|
|
5646
5760
|
const size = statSync5(entry.path).size;
|
|
5647
5761
|
if (size <= 0 || size !== entry.bytes) continue;
|
|
5648
5762
|
const frames = await probeFrames(entry.path);
|
|
@@ -5782,7 +5896,7 @@ var ALLOWED_STRING_DATA_KEYS = /* @__PURE__ */ new Set([
|
|
|
5782
5896
|
"stagePhase",
|
|
5783
5897
|
// Full-fidelity fast-capture fallback trigger — the specific reason
|
|
5784
5898
|
// drawElement gated off ("filter:blur", "filter:drop-shadow",
|
|
5785
|
-
// "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", …).
|
|
5899
|
+
// "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", "webgl_unavailable", …).
|
|
5786
5900
|
// Populated on the `capture_fallback_profile` checkpoint emitted by
|
|
5787
5901
|
// `fallbackCaptureProfile.ts` when the operator opts into
|
|
5788
5902
|
// `HF_PROFILE_FALLBACK_CAPTURE=true` so downstream metric consumers can
|
|
@@ -6221,17 +6335,17 @@ function countAuthoredTimedClips(html) {
|
|
|
6221
6335
|
}
|
|
6222
6336
|
|
|
6223
6337
|
// src/services/render/stages/compileStage.ts
|
|
6224
|
-
import { existsSync as
|
|
6338
|
+
import { existsSync as existsSync12, writeFileSync as writeFileSync5 } from "node:fs";
|
|
6225
6339
|
import { join as join19 } from "node:path";
|
|
6226
6340
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
6227
6341
|
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
6228
6342
|
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
6229
6343
|
|
|
6230
6344
|
// src/services/htmlCompiler.ts
|
|
6231
|
-
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
6345
|
+
import { decodedUrlPath as decodedUrlPath2 } from "@hyperframes/parsers/asset-paths";
|
|
6232
6346
|
import { createHash as createHash6 } from "node:crypto";
|
|
6233
|
-
import { createReadStream as createReadStream2, existsSync as
|
|
6234
|
-
import { join as join18, dirname as dirname8, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
|
|
6347
|
+
import { createReadStream as createReadStream2, existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync7 } from "fs";
|
|
6348
|
+
import { join as join18, dirname as dirname8, resolve as resolve7, basename as basename4, relative as relative2, sep as sep2 } from "path";
|
|
6235
6349
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
6236
6350
|
import {
|
|
6237
6351
|
COMPOSITION_SOURCE_URL,
|
|
@@ -6477,7 +6591,7 @@ import {
|
|
|
6477
6591
|
|
|
6478
6592
|
// src/services/animatedGifPrep.ts
|
|
6479
6593
|
import { createHash as createHash4 } from "node:crypto";
|
|
6480
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
6594
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync5, rmSync as rmSync9, statSync as statSync6 } from "node:fs";
|
|
6481
6595
|
import { dirname as dirname6, isAbsolute as isAbsolute2, join as join16, resolve as resolve5 } from "node:path";
|
|
6482
6596
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
6483
6597
|
import { parseAnimatedGifMetadata } from "@hyperframes/core";
|
|
@@ -6516,14 +6630,14 @@ function resolveLocalImageSourcePath(src, options) {
|
|
|
6516
6630
|
const { basePath } = splitUrlSuffix(trimmed);
|
|
6517
6631
|
const normalizedBase = normalizeRelPath(basePath);
|
|
6518
6632
|
const mapped = options.sourceAssets?.get(trimmed) ?? options.sourceAssets?.get(basePath) ?? options.sourceAssets?.get(normalizedBase);
|
|
6519
|
-
if (mapped &&
|
|
6633
|
+
if (mapped && existsSync9(mapped)) return mapped;
|
|
6520
6634
|
if (isHttpUrl(trimmed)) return null;
|
|
6521
6635
|
const projectRelative = basePath.startsWith("/") ? basePath.slice(1) : basePath;
|
|
6522
6636
|
const candidates = [
|
|
6523
6637
|
isAbsolute2(basePath) ? basePath : resolve5(options.projectDir, projectRelative),
|
|
6524
6638
|
resolve5(options.downloadDir, normalizedBase)
|
|
6525
6639
|
];
|
|
6526
|
-
return candidates.find((candidate) =>
|
|
6640
|
+
return candidates.find((candidate) => existsSync9(candidate)) ?? null;
|
|
6527
6641
|
}
|
|
6528
6642
|
function isUsableFile(path) {
|
|
6529
6643
|
try {
|
|
@@ -6634,10 +6748,10 @@ async function runAnimatedGifTranscode(request) {
|
|
|
6634
6748
|
throw result.error ?? new Error(`Animated GIF transcode failed (${result.exitCode}): ${result.stderr.slice(-500)}`);
|
|
6635
6749
|
}
|
|
6636
6750
|
async function ensurePreparedWebm(input) {
|
|
6637
|
-
if (!
|
|
6751
|
+
if (!existsSync9(dirname6(input.cachePath))) {
|
|
6638
6752
|
mkdirSync7(dirname6(input.cachePath), { recursive: true });
|
|
6639
6753
|
}
|
|
6640
|
-
if (!
|
|
6754
|
+
if (!existsSync9(dirname6(input.outputPath))) {
|
|
6641
6755
|
mkdirSync7(dirname6(input.outputPath), { recursive: true });
|
|
6642
6756
|
}
|
|
6643
6757
|
if (!isUsableFile(input.cachePath)) {
|
|
@@ -7112,7 +7226,7 @@ import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-serve
|
|
|
7112
7226
|
import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
|
|
7113
7227
|
|
|
7114
7228
|
// src/services/assetMediaType.ts
|
|
7115
|
-
import { existsSync as
|
|
7229
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
7116
7230
|
import {
|
|
7117
7231
|
fingerprintElementId,
|
|
7118
7232
|
isNotMediaPayload,
|
|
@@ -7185,7 +7299,7 @@ async function preflightCompositionAssetMediaTypes(input) {
|
|
|
7185
7299
|
input.projectDir,
|
|
7186
7300
|
input.compiledDir
|
|
7187
7301
|
);
|
|
7188
|
-
if (!
|
|
7302
|
+
if (!existsSync10(resolvedPath)) continue;
|
|
7189
7303
|
byPath.set(resolvedPath, [...byPath.get(resolvedPath) ?? [], reference]);
|
|
7190
7304
|
}
|
|
7191
7305
|
const mismatches = [];
|
|
@@ -7273,6 +7387,14 @@ Check that each file referenced by data-composition-src contains valid HTML with
|
|
|
7273
7387
|
this.problems = problems;
|
|
7274
7388
|
}
|
|
7275
7389
|
};
|
|
7390
|
+
function normalizeCompositionSrcs(html, projectDir) {
|
|
7391
|
+
return html.replace(/(\bdata-composition-src=)(["'])([^"']*)\2/g, (whole, name, quote, value) => {
|
|
7392
|
+
if (!value.includes("%") && !value.startsWith("/")) return whole;
|
|
7393
|
+
const file = resolveCompositionSrc(projectDir, value);
|
|
7394
|
+
if (file === null) return whole;
|
|
7395
|
+
return `${name}${quote}${relative2(projectDir, file).split(sep2).join("/")}${quote}`;
|
|
7396
|
+
});
|
|
7397
|
+
}
|
|
7276
7398
|
function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */ new Set()) {
|
|
7277
7399
|
const { document: document2 } = parseHTML5(html);
|
|
7278
7400
|
const hosts = [...document2.querySelectorAll("[data-composition-src]")];
|
|
@@ -7281,7 +7403,11 @@ function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */
|
|
|
7281
7403
|
const srcPath = el.getAttribute("data-composition-src");
|
|
7282
7404
|
if (!srcPath) continue;
|
|
7283
7405
|
if (isUnresolvedAssetPlaceholder(srcPath)) continue;
|
|
7284
|
-
const filePath =
|
|
7406
|
+
const filePath = resolveCompositionSrc(projectDir, srcPath);
|
|
7407
|
+
if (filePath === null) {
|
|
7408
|
+
problems.push({ srcPath, detail: "it is outside the project folder" });
|
|
7409
|
+
continue;
|
|
7410
|
+
}
|
|
7285
7411
|
if (visited.has(filePath)) continue;
|
|
7286
7412
|
const read = readProjectFile(filePath);
|
|
7287
7413
|
if (read.kind === "missing") {
|
|
@@ -7427,7 +7553,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
7427
7553
|
let filePath = src;
|
|
7428
7554
|
if (isHttpUrl(src)) {
|
|
7429
7555
|
const remoteDir = join18(downloadDir, REMOTE_MEDIA_SUBDIR);
|
|
7430
|
-
if (!
|
|
7556
|
+
if (!existsSync11(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
|
|
7431
7557
|
try {
|
|
7432
7558
|
filePath = await downloadToTemp(src, remoteDir, void 0, void 0, void 0, {
|
|
7433
7559
|
onTelemetry: logRemoteDownloadTelemetry
|
|
@@ -7436,11 +7562,11 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
7436
7562
|
return { duration: null, resolvedPath: src };
|
|
7437
7563
|
}
|
|
7438
7564
|
} else {
|
|
7439
|
-
filePath =
|
|
7565
|
+
filePath = decodedUrlPath2(src);
|
|
7440
7566
|
if (!filePath.startsWith("/")) filePath = join18(baseDir, filePath);
|
|
7441
7567
|
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
7442
7568
|
}
|
|
7443
|
-
if (!isHttpUrl(filePath) && !
|
|
7569
|
+
if (!isHttpUrl(filePath) && !existsSync11(filePath)) {
|
|
7444
7570
|
return { duration: null, resolvedPath: filePath };
|
|
7445
7571
|
}
|
|
7446
7572
|
const withSrcContext = (error) => {
|
|
@@ -7581,7 +7707,8 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
7581
7707
|
for (const el of compEls) {
|
|
7582
7708
|
const srcPath = el.getAttribute("data-composition-src");
|
|
7583
7709
|
if (!srcPath) continue;
|
|
7584
|
-
const filePath =
|
|
7710
|
+
const filePath = resolveCompositionSrc(projectDir, srcPath);
|
|
7711
|
+
if (filePath === null) continue;
|
|
7585
7712
|
if (visited.has(filePath)) {
|
|
7586
7713
|
continue;
|
|
7587
7714
|
}
|
|
@@ -7589,7 +7716,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
7589
7716
|
if (read.kind !== "file") {
|
|
7590
7717
|
continue;
|
|
7591
7718
|
}
|
|
7592
|
-
const rawSubHtml = read.text;
|
|
7719
|
+
const rawSubHtml = normalizeCompositionSrcs(read.text, projectDir);
|
|
7593
7720
|
const nestedVisited = new Set(visited);
|
|
7594
7721
|
nestedVisited.add(filePath);
|
|
7595
7722
|
workItems.push({ srcPath, filePath, rawSubHtml, nestedVisited });
|
|
@@ -7777,8 +7904,9 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
|
|
|
7777
7904
|
resolveHtml: (srcPath) => {
|
|
7778
7905
|
let compHtml = subCompositions.get(srcPath) || null;
|
|
7779
7906
|
if (!compHtml) {
|
|
7780
|
-
const
|
|
7781
|
-
|
|
7907
|
+
const filePath = resolveCompositionSrc(projectDir, srcPath);
|
|
7908
|
+
const read = filePath ? readProjectFile(filePath) : null;
|
|
7909
|
+
if (read?.kind === "file") {
|
|
7782
7910
|
compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
|
|
7783
7911
|
}
|
|
7784
7912
|
}
|
|
@@ -7787,7 +7915,7 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
|
|
|
7787
7915
|
parseHtml: (htmlStr) => parseHTML5(htmlStr).document,
|
|
7788
7916
|
// Mirrors the preview bundler: a sub-composition's SIBLING assets resolve
|
|
7789
7917
|
// against its own directory, project-root refs stay as authored.
|
|
7790
|
-
assetExists: (path) =>
|
|
7918
|
+
assetExists: (path) => existsSync11(resolve7(projectDir, path)),
|
|
7791
7919
|
// Preserve the authored root wrapper as a child of the host, matching
|
|
7792
7920
|
// the preview bundler's shape (htmlBundler.ts's prepareFlattenedInnerRoot,
|
|
7793
7921
|
// which the runtime compositionLoader mirrors with its own copy for the
|
|
@@ -7978,7 +8106,7 @@ function collectExternalAssets(html, projectDir) {
|
|
|
7978
8106
|
if (isPathInside2(absPath, absProjectDir)) {
|
|
7979
8107
|
return null;
|
|
7980
8108
|
}
|
|
7981
|
-
if (!
|
|
8109
|
+
if (!existsSync11(absPath)) return null;
|
|
7982
8110
|
const safeKey = toExternalAssetKey(absPath);
|
|
7983
8111
|
externalAssets.set(safeKey, absPath);
|
|
7984
8112
|
return safeKey;
|
|
@@ -8028,7 +8156,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
|
|
|
8028
8156
|
var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
|
|
8029
8157
|
async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
|
|
8030
8158
|
if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
|
|
8031
|
-
if (!
|
|
8159
|
+
if (!existsSync11(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
|
|
8032
8160
|
const urlToLocal = /* @__PURE__ */ new Map();
|
|
8033
8161
|
await Promise.all(
|
|
8034
8162
|
[...urlSet].map(async (url) => {
|
|
@@ -8210,14 +8338,14 @@ function projectStylesheetFontFamilies(html, projectDir) {
|
|
|
8210
8338
|
for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
|
|
8211
8339
|
const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
8212
8340
|
if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
|
|
8213
|
-
const sheet = resolve7(projectDir,
|
|
8214
|
-
if (!isPathInside2(sheet, projectDir) || !
|
|
8341
|
+
const sheet = resolve7(projectDir, decodedUrlPath2(href).replace(/^(?:\.?\/)+/, ""));
|
|
8342
|
+
if (!isPathInside2(sheet, projectDir) || !existsSync11(sheet)) continue;
|
|
8215
8343
|
for (const face of extractFontFaceBlocks(readFileSync7(sheet, "utf8"))) {
|
|
8216
8344
|
const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
8217
8345
|
const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
|
|
8218
|
-
const path =
|
|
8346
|
+
const path = decodedUrlPath2(url);
|
|
8219
8347
|
const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(dirname8(sheet), path);
|
|
8220
|
-
return isPathInside2(file, projectDir) &&
|
|
8348
|
+
return isPathInside2(file, projectDir) && existsSync11(file);
|
|
8221
8349
|
});
|
|
8222
8350
|
if (family && shipsFile) families.push(family);
|
|
8223
8351
|
}
|
|
@@ -8352,7 +8480,7 @@ function renderGsapDist() {
|
|
|
8352
8480
|
}
|
|
8353
8481
|
function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
|
|
8354
8482
|
let gsapDist;
|
|
8355
|
-
const exists = (path) =>
|
|
8483
|
+
const exists = (path) => existsSync11(resolve7(projectDir, path));
|
|
8356
8484
|
return html.replace(
|
|
8357
8485
|
/(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
|
|
8358
8486
|
(full, prefix, src, file, suffix) => {
|
|
@@ -8369,7 +8497,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
8369
8497
|
const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
|
|
8370
8498
|
if (!entryPath.includes("/")) return html;
|
|
8371
8499
|
const { document: document2 } = parseHTML5(html);
|
|
8372
|
-
const assetExists = (path) =>
|
|
8500
|
+
const assetExists = (path) => existsSync11(resolve7(projectDir, path));
|
|
8373
8501
|
rewriteAssetPaths(
|
|
8374
8502
|
document2.querySelectorAll("[src], [href], [srcset]"),
|
|
8375
8503
|
entryPath,
|
|
@@ -8391,7 +8519,10 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
8391
8519
|
}
|
|
8392
8520
|
async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
|
|
8393
8521
|
const entryHtml = rebaseDirectEntryAssetPaths(readTextFile(htmlPath), projectDir, htmlPath);
|
|
8394
|
-
const rawHtml =
|
|
8522
|
+
const rawHtml = normalizeCompositionSrcs(
|
|
8523
|
+
rewriteUnresolvableGsapToCdn(entryHtml, projectDir),
|
|
8524
|
+
projectDir
|
|
8525
|
+
);
|
|
8395
8526
|
assertSubCompositionsUsable(rawHtml, projectDir);
|
|
8396
8527
|
const probes = /* @__PURE__ */ new Map();
|
|
8397
8528
|
const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
|
|
@@ -8936,7 +9067,7 @@ function resolveEntryHtmlPath(input) {
|
|
|
8936
9067
|
const rawEntry = readTextFile2(htmlPath);
|
|
8937
9068
|
if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
|
|
8938
9069
|
const projectIndexPath = join19(projectDir, "index.html");
|
|
8939
|
-
if (!
|
|
9070
|
+
if (!existsSync12(projectIndexPath)) {
|
|
8940
9071
|
throw new Error(
|
|
8941
9072
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
8942
9073
|
);
|
|
@@ -9770,7 +9901,7 @@ function validateRenderDuration(input) {
|
|
|
9770
9901
|
}
|
|
9771
9902
|
|
|
9772
9903
|
// src/services/render/stages/extractVideosStage.ts
|
|
9773
|
-
import { existsSync as
|
|
9904
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
9774
9905
|
import { join as join21 } from "node:path";
|
|
9775
9906
|
import {
|
|
9776
9907
|
classifyVideoExtractionError,
|
|
@@ -10040,7 +10171,7 @@ async function runExtractVideosStage(input) {
|
|
|
10040
10171
|
const probeFailures = await Promise.all(
|
|
10041
10172
|
composition.videos.map(async (v) => {
|
|
10042
10173
|
const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
|
|
10043
|
-
if (!
|
|
10174
|
+
if (!existsSync13(videoPath)) return null;
|
|
10044
10175
|
try {
|
|
10045
10176
|
const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry2(() => extractMediaMetadata3(videoPath), {
|
|
10046
10177
|
signal: abortSignal,
|
|
@@ -10078,7 +10209,7 @@ async function runExtractVideosStage(input) {
|
|
|
10078
10209
|
const probed = await Promise.all(
|
|
10079
10210
|
composition.images.map(async (img) => {
|
|
10080
10211
|
const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
|
|
10081
|
-
if (!
|
|
10212
|
+
if (!existsSync13(imgPath)) return null;
|
|
10082
10213
|
const colorSpace = await probeColorSpaceSafely(imgPath, log);
|
|
10083
10214
|
if (isHdrColorSpace2(colorSpace)) {
|
|
10084
10215
|
nativeHdrImageIds.add(img.id);
|
|
@@ -10198,7 +10329,7 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
10198
10329
|
videos.map(async (video) => {
|
|
10199
10330
|
const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
|
|
10200
10331
|
const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
|
|
10201
|
-
if (!isHttpUrl2(readFrom) && !
|
|
10332
|
+
if (!isHttpUrl2(readFrom) && !existsSync13(readFrom)) return null;
|
|
10202
10333
|
const metadata = await extractMediaMetadata3(readFrom);
|
|
10203
10334
|
const window2 = await resolveFinalFrameExtractionWindow2(
|
|
10204
10335
|
readFrom,
|
|
@@ -10218,7 +10349,7 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
10218
10349
|
}
|
|
10219
10350
|
|
|
10220
10351
|
// src/services/render/videoLayerPassthroughPlan.ts
|
|
10221
|
-
import { existsSync as
|
|
10352
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
10222
10353
|
import {
|
|
10223
10354
|
buildVideoLayerInput,
|
|
10224
10355
|
isHttpUrl as isHttpUrl3,
|
|
@@ -10286,7 +10417,7 @@ async function planVideoLayerPassthrough(input) {
|
|
|
10286
10417
|
const { video } = q;
|
|
10287
10418
|
if (isHttpUrl3(video.src)) return { reason: "remote_source" };
|
|
10288
10419
|
const path = resolveProjectRelativeSrc4(video.src, input.projectDir, input.compiledDir);
|
|
10289
|
-
if (!
|
|
10420
|
+
if (!existsSync14(path)) return { reason: "source_missing" };
|
|
10290
10421
|
const layout = await probeVideoLayout(input.page, video.id, input.canvas, input.duration);
|
|
10291
10422
|
if ("reason" in layout) return layout;
|
|
10292
10423
|
const layer = await buildVideoLayerInput(
|
|
@@ -10301,7 +10432,7 @@ async function planVideoLayerPassthrough(input) {
|
|
|
10301
10432
|
}
|
|
10302
10433
|
|
|
10303
10434
|
// src/services/render/stages/audioStage.ts
|
|
10304
|
-
import { existsSync as
|
|
10435
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
10305
10436
|
import { join as join24 } from "node:path";
|
|
10306
10437
|
import {
|
|
10307
10438
|
MIXED_AUDIO_FILENAME,
|
|
@@ -10312,7 +10443,7 @@ import {
|
|
|
10312
10443
|
} from "@hyperframes/engine";
|
|
10313
10444
|
|
|
10314
10445
|
// src/services/render/audioDeliveryLimit.ts
|
|
10315
|
-
import { existsSync as
|
|
10446
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
10316
10447
|
import { dirname as dirname10, join as join23 } from "node:path";
|
|
10317
10448
|
import {
|
|
10318
10449
|
applyAudioFxChain,
|
|
@@ -10821,7 +10952,7 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
10821
10952
|
applyChain: applyAudioFxChain,
|
|
10822
10953
|
encode: encodeAac2,
|
|
10823
10954
|
rename: renameWithRetry6,
|
|
10824
|
-
sourceExists:
|
|
10955
|
+
sourceExists: existsSync15
|
|
10825
10956
|
}) {
|
|
10826
10957
|
const { audioPath, sourcePath, signal, timeoutMs } = input;
|
|
10827
10958
|
if (!deps.sourceExists(sourcePath)) return { success: true };
|
|
@@ -10916,7 +11047,7 @@ async function runAudioStageScoped(input) {
|
|
|
10916
11047
|
);
|
|
10917
11048
|
audioLoweredDb = guard.loweredDb;
|
|
10918
11049
|
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
10919
|
-
if (input.keepDeliverySource && !guard.sourceStale &&
|
|
11050
|
+
if (input.keepDeliverySource && !guard.sourceStale && existsSync16(pcmSourcePath)) {
|
|
10920
11051
|
deliverySourcePath = pcmSourcePath;
|
|
10921
11052
|
}
|
|
10922
11053
|
}
|
|
@@ -11044,7 +11175,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
11044
11175
|
}
|
|
11045
11176
|
|
|
11046
11177
|
// src/services/render/stages/captureStage.ts
|
|
11047
|
-
import { existsSync as
|
|
11178
|
+
import { existsSync as existsSync17, readdirSync as readdirSync4, statfsSync as statfsSync2 } from "node:fs";
|
|
11048
11179
|
import { join as join25 } from "node:path";
|
|
11049
11180
|
import {
|
|
11050
11181
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
@@ -11115,7 +11246,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
11115
11246
|
}
|
|
11116
11247
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
11117
11248
|
let total = sampleDirectoryBytes(framesDir);
|
|
11118
|
-
if (!
|
|
11249
|
+
if (!existsSync17(workDir)) return total;
|
|
11119
11250
|
for (const attempt of readdirSync4(workDir)) {
|
|
11120
11251
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
11121
11252
|
total += sampleDirectoryBytes(join25(workDir, attempt));
|
|
@@ -11300,7 +11431,11 @@ async function runSequentialDiskCapture(input, captureOptions, captureCfg, check
|
|
|
11300
11431
|
prepareCaptureSessionForReuse3(session, framesDir, videoInjector);
|
|
11301
11432
|
probeSession = null;
|
|
11302
11433
|
}
|
|
11303
|
-
await ensureSessionReady(session
|
|
11434
|
+
await ensureSessionReady(session, {
|
|
11435
|
+
frameIndex: resumeFrom,
|
|
11436
|
+
totalFrames,
|
|
11437
|
+
abortSignal: input.abortSignal
|
|
11438
|
+
});
|
|
11304
11439
|
assertAttemptActive();
|
|
11305
11440
|
lastBrowserConsole = session.browserConsoleBuffer;
|
|
11306
11441
|
await captureSessionFrames(session, {
|
|
@@ -11352,13 +11487,13 @@ function planTransientResume(error, ctx) {
|
|
|
11352
11487
|
if (!firstMissing) return null;
|
|
11353
11488
|
return isTransientCaptureRetryEligible(failure.kind, missing, ctx.retriesUsed) ? firstMissing.startFrame : null;
|
|
11354
11489
|
}
|
|
11355
|
-
async function ensureSessionReady(session) {
|
|
11490
|
+
async function ensureSessionReady(session, budget) {
|
|
11356
11491
|
if (!session.isInitialized) {
|
|
11357
11492
|
await initializeSession5(session);
|
|
11358
11493
|
return;
|
|
11359
11494
|
}
|
|
11360
11495
|
if (process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true") {
|
|
11361
|
-
await completeDeferredDrawElementInit3(session);
|
|
11496
|
+
await createSessionInitBudget(budget)(completeDeferredDrawElementInit3(session), session);
|
|
11362
11497
|
}
|
|
11363
11498
|
}
|
|
11364
11499
|
async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resumeFrom, checkDiskProjection }) {
|
|
@@ -11418,7 +11553,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
|
|
|
11418
11553
|
}
|
|
11419
11554
|
|
|
11420
11555
|
// src/services/render/stages/captureHdrStage.ts
|
|
11421
|
-
import { existsSync as
|
|
11556
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync10 } from "node:fs";
|
|
11422
11557
|
import { join as join29 } from "node:path";
|
|
11423
11558
|
import {
|
|
11424
11559
|
closeCaptureSession as closeCaptureSession7,
|
|
@@ -11664,7 +11799,7 @@ import { Worker } from "node:worker_threads";
|
|
|
11664
11799
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
11665
11800
|
import { dirname as dirname11, join as join27 } from "node:path";
|
|
11666
11801
|
import { createRequire } from "node:module";
|
|
11667
|
-
import { existsSync as
|
|
11802
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
11668
11803
|
import { cpus as cpus2 } from "node:os";
|
|
11669
11804
|
function resolveWorkerEntry(explicit) {
|
|
11670
11805
|
if (explicit && explicit.length > 0) {
|
|
@@ -11677,7 +11812,7 @@ function resolveWorkerEntry(explicit) {
|
|
|
11677
11812
|
}
|
|
11678
11813
|
const moduleDir = dirname11(fileURLToPath3(import.meta.url));
|
|
11679
11814
|
const jsPath = join27(moduleDir, "shaderTransitionWorker.js");
|
|
11680
|
-
if (
|
|
11815
|
+
if (existsSync18(jsPath)) return { path: jsPath, isTs: false };
|
|
11681
11816
|
const tsPath = join27(moduleDir, "shaderTransitionWorker.ts");
|
|
11682
11817
|
return { path: tsPath, isTs: true };
|
|
11683
11818
|
}
|
|
@@ -12238,7 +12373,7 @@ async function runCaptureHdrStage(input) {
|
|
|
12238
12373
|
}
|
|
12239
12374
|
const debugDumpEnabled = process.env.KEEP_TEMP === "1";
|
|
12240
12375
|
const debugDumpDir = debugDumpEnabled ? join29(framesDir, "debug-composite") : null;
|
|
12241
|
-
if (debugDumpDir && !
|
|
12376
|
+
if (debugDumpDir && !existsSync19(debugDumpDir)) {
|
|
12242
12377
|
mkdirSync10(debugDumpDir, { recursive: true });
|
|
12243
12378
|
}
|
|
12244
12379
|
const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
|
|
@@ -12395,7 +12530,7 @@ async function runCaptureHdrStage(input) {
|
|
|
12395
12530
|
// src/services/render/stages/encodeStage.ts
|
|
12396
12531
|
import {
|
|
12397
12532
|
copyFileSync as copyFileSync4,
|
|
12398
|
-
existsSync as
|
|
12533
|
+
existsSync as existsSync20,
|
|
12399
12534
|
mkdirSync as mkdirSync11,
|
|
12400
12535
|
readdirSync as readdirSync5,
|
|
12401
12536
|
readFileSync as readFileSync8,
|
|
@@ -12529,7 +12664,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
12529
12664
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
12530
12665
|
if (failure) return failure;
|
|
12531
12666
|
}
|
|
12532
|
-
const fileSize =
|
|
12667
|
+
const fileSize = existsSync20(outputPath) ? statSync8(outputPath).size : 0;
|
|
12533
12668
|
return {
|
|
12534
12669
|
success: true,
|
|
12535
12670
|
outputPath,
|
|
@@ -12690,7 +12825,7 @@ async function runEncodeStage(input) {
|
|
|
12690
12825
|
const stage5Start = Date.now();
|
|
12691
12826
|
if (isPngSequence) {
|
|
12692
12827
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
12693
|
-
if (!
|
|
12828
|
+
if (!existsSync20(outputPath)) mkdirSync11(outputPath, { recursive: true });
|
|
12694
12829
|
const captured = readdirSync5(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
12695
12830
|
if (captured.length === 0) {
|
|
12696
12831
|
throw new Error(
|
|
@@ -12701,7 +12836,7 @@ async function runEncodeStage(input) {
|
|
|
12701
12836
|
const dst = join30(outputPath, formatExportFrameName(i, "png"));
|
|
12702
12837
|
copyFileSync4(join30(framesDir, name), dst);
|
|
12703
12838
|
});
|
|
12704
|
-
if (hasAudio && audioOutputPath &&
|
|
12839
|
+
if (hasAudio && audioOutputPath && existsSync20(audioOutputPath)) {
|
|
12705
12840
|
copyFileSync4(audioOutputPath, join30(outputPath, MIXED_AUDIO_FILENAME2));
|
|
12706
12841
|
log.info(
|
|
12707
12842
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
@@ -13090,7 +13225,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
13090
13225
|
let rangeStart = null;
|
|
13091
13226
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
13092
13227
|
const framePath = join31(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
13093
|
-
const missing = !
|
|
13228
|
+
const missing = !existsSync21(framePath) || statSync9(framePath).size <= 8;
|
|
13094
13229
|
if (missing && rangeStart === null) {
|
|
13095
13230
|
rangeStart = frameIndex;
|
|
13096
13231
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -13215,7 +13350,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
|
13215
13350
|
let captured = 0;
|
|
13216
13351
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
13217
13352
|
const framePath = join31(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
13218
|
-
if (
|
|
13353
|
+
if (existsSync21(framePath)) captured++;
|
|
13219
13354
|
}
|
|
13220
13355
|
return captured;
|
|
13221
13356
|
}
|
|
@@ -13880,7 +14015,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
13880
14015
|
validateDeliveryEncoderConfig(job.config);
|
|
13881
14016
|
const debugDir = resolveRenderDebugDir();
|
|
13882
14017
|
const outputDir = dirname13(outputPath);
|
|
13883
|
-
if (!
|
|
14018
|
+
if (!existsSync21(outputDir)) mkdirSync12(outputDir, { recursive: true });
|
|
13884
14019
|
const workDir = job.config.debug ? join31(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
|
|
13885
14020
|
const pipelineStart = Date.now();
|
|
13886
14021
|
const baseLog = job.config.logger ?? defaultLogger;
|
|
@@ -14022,7 +14157,7 @@ async function executeRenderPipeline(input) {
|
|
|
14022
14157
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
14023
14158
|
assertNotAborted();
|
|
14024
14159
|
assertConfiguredFfmpegBinariesExist();
|
|
14025
|
-
if (!
|
|
14160
|
+
if (!existsSync21(workDir)) mkdirSync12(workDir, { recursive: true });
|
|
14026
14161
|
if (job.config.debug) {
|
|
14027
14162
|
log.info("[Render] Debug artifacts enabled", { workDir, logPath });
|
|
14028
14163
|
}
|
|
@@ -14052,7 +14187,7 @@ async function executeRenderPipeline(input) {
|
|
|
14052
14187
|
});
|
|
14053
14188
|
const entryFile = job.config.entryFile || "index.html";
|
|
14054
14189
|
const htmlPath = join31(projectDir, entryFile);
|
|
14055
|
-
if (!
|
|
14190
|
+
if (!existsSync21(htmlPath)) {
|
|
14056
14191
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
14057
14192
|
}
|
|
14058
14193
|
assertNotAborted();
|
|
@@ -14367,9 +14502,11 @@ async function executeRenderPipeline(input) {
|
|
|
14367
14502
|
);
|
|
14368
14503
|
pendingAudio = audioStage.catch(() => void 0);
|
|
14369
14504
|
const stage4Start = Date.now();
|
|
14370
|
-
|
|
14505
|
+
const EFFECT_SCAN_STAGE = "Checking effects";
|
|
14506
|
+
const startingFrameCapture = () => updateJobStatus(job, "rendering", "Starting frame capture", 25, onProgress, {
|
|
14371
14507
|
code: "start_browsers"
|
|
14372
14508
|
});
|
|
14509
|
+
startingFrameCapture();
|
|
14373
14510
|
if (!fileServer) {
|
|
14374
14511
|
const fileServerStart = observability.stageStart("file_server", { reused: false });
|
|
14375
14512
|
try {
|
|
@@ -14394,7 +14531,7 @@ async function executeRenderPipeline(input) {
|
|
|
14394
14531
|
throw new Error("File server failed to initialize before frame capture");
|
|
14395
14532
|
}
|
|
14396
14533
|
const framesDir = join31(workDir, "captured-frames");
|
|
14397
|
-
if (!
|
|
14534
|
+
if (!existsSync21(framesDir)) mkdirSync12(framesDir, { recursive: true });
|
|
14398
14535
|
updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
|
|
14399
14536
|
code: "start_browsers"
|
|
14400
14537
|
});
|
|
@@ -14402,6 +14539,7 @@ async function executeRenderPipeline(input) {
|
|
|
14402
14539
|
chromePath: resolveHeadlessShellPath(cfg),
|
|
14403
14540
|
browserTimeout: cfg.browserTimeout
|
|
14404
14541
|
});
|
|
14542
|
+
startingFrameCapture();
|
|
14405
14543
|
captureForceScreenshot = applyConcreteGpuScreenshotClamp(
|
|
14406
14544
|
captureForceScreenshot,
|
|
14407
14545
|
resolvedBrowserGpuMode,
|
|
@@ -14450,10 +14588,13 @@ async function executeRenderPipeline(input) {
|
|
|
14450
14588
|
onEffectScanProgress: (scanned, total) => updateJobStatus(
|
|
14451
14589
|
job,
|
|
14452
14590
|
"rendering",
|
|
14453
|
-
|
|
14591
|
+
`${EFFECT_SCAN_STAGE}, ${scanned} of ${total} frames`,
|
|
14454
14592
|
25,
|
|
14455
14593
|
onProgress
|
|
14456
14594
|
),
|
|
14595
|
+
onEffectScanEnd: () => {
|
|
14596
|
+
if (job.currentStage.startsWith(EFFECT_SCAN_STAGE)) startingFrameCapture();
|
|
14597
|
+
},
|
|
14457
14598
|
requiresWebGpu: compositionRequiresWebGpu2(compiled.html),
|
|
14458
14599
|
// Live route for Chrome memory (spec §5 Phase −1). The aggregate route
|
|
14459
14600
|
// through CapturePerfSummary only exists on success; a mid-capture
|
|
@@ -14721,6 +14862,7 @@ async function executeRenderPipeline(input) {
|
|
|
14721
14862
|
} finally {
|
|
14722
14863
|
calibrationFrames?.release();
|
|
14723
14864
|
}
|
|
14865
|
+
startingFrameCapture();
|
|
14724
14866
|
captureCalibration = outcome.calibration;
|
|
14725
14867
|
calibrationEvaluateTimedOut = outcome.calibrationEvaluateTimedOut;
|
|
14726
14868
|
captureForceScreenshot = outcome.forceScreenshot;
|
|
@@ -15779,7 +15921,7 @@ async function executeRenderPipeline(input) {
|
|
|
15779
15921
|
})
|
|
15780
15922
|
);
|
|
15781
15923
|
const totalElapsed = Date.now() - pipelineStart;
|
|
15782
|
-
const tmpPeakBytes =
|
|
15924
|
+
const tmpPeakBytes = existsSync21(workDir) ? sampleDirectoryBytes(workDir) : 0;
|
|
15783
15925
|
recordTransientRetryObservability();
|
|
15784
15926
|
observability.checkpoint("pipeline", "artifact validated", { totalElapsedMs: totalElapsed });
|
|
15785
15927
|
const observabilitySummary = observability.summary({
|
|
@@ -15860,7 +16002,7 @@ async function executeRenderPipeline(input) {
|
|
|
15860
16002
|
}
|
|
15861
16003
|
}
|
|
15862
16004
|
if (job.config.debug) {
|
|
15863
|
-
if (!isPngSequence && !isHls &&
|
|
16005
|
+
if (!isPngSequence && !isHls && existsSync21(stagedOutputPath)) {
|
|
15864
16006
|
const debugOutput = join31(workDir, `output${videoExt}`);
|
|
15865
16007
|
copyFileSync5(stagedOutputPath, debugOutput);
|
|
15866
16008
|
}
|
|
@@ -15972,7 +16114,7 @@ async function executeRenderPipeline(input) {
|
|
|
15972
16114
|
|
|
15973
16115
|
// src/services/hyperframeLint.ts
|
|
15974
16116
|
import {
|
|
15975
|
-
existsSync as
|
|
16117
|
+
existsSync as existsSync22,
|
|
15976
16118
|
readFileSync as readFileSync9,
|
|
15977
16119
|
statSync as statSync10,
|
|
15978
16120
|
openSync as openSync5,
|
|
@@ -15980,7 +16122,7 @@ import {
|
|
|
15980
16122
|
closeSync as closeSync5,
|
|
15981
16123
|
constants as constants3
|
|
15982
16124
|
} from "node:fs";
|
|
15983
|
-
import { resolve as resolve9, join as join32, relative as relative3, isAbsolute as isAbsolute3, sep as
|
|
16125
|
+
import { resolve as resolve9, join as join32, relative as relative3, isAbsolute as isAbsolute3, sep as sep3 } from "node:path";
|
|
15984
16126
|
import { lintHyperframeHtml } from "@hyperframes/lint";
|
|
15985
16127
|
function isStringRecord(value) {
|
|
15986
16128
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -16026,7 +16168,7 @@ function readEntryHtml(filePath) {
|
|
|
16026
16168
|
}
|
|
16027
16169
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
16028
16170
|
const absProjectDir = resolve9(projectDir);
|
|
16029
|
-
if (!
|
|
16171
|
+
if (!existsSync22(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
|
|
16030
16172
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
16031
16173
|
}
|
|
16032
16174
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -16035,7 +16177,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
16035
16177
|
for (const entryFile of entryCandidates) {
|
|
16036
16178
|
const absoluteEntryPath = resolve9(absProjectDir, entryFile);
|
|
16037
16179
|
const entryRelativePath = relative3(absProjectDir, absoluteEntryPath);
|
|
16038
|
-
if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${
|
|
16180
|
+
if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${sep3}`) || isAbsolute3(entryRelativePath)) {
|
|
16039
16181
|
return { error: `Entry file must stay inside project directory: ${entryFile}` };
|
|
16040
16182
|
}
|
|
16041
16183
|
const html = readEntryHtml(absoluteEntryPath);
|
|
@@ -16095,7 +16237,7 @@ async function runHyperframeLint(prepared) {
|
|
|
16095
16237
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
16096
16238
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
16097
16239
|
import { dirname as dirname14, join as join33 } from "node:path";
|
|
16098
|
-
import { existsSync as
|
|
16240
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
16099
16241
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
16100
16242
|
async function startHealthWorker(options = {}) {
|
|
16101
16243
|
const log = options.logger ?? defaultLogger2();
|
|
@@ -16172,7 +16314,7 @@ function resolveWorkerEntry2() {
|
|
|
16172
16314
|
const here = dirname14(fileURLToPath5(import.meta.url));
|
|
16173
16315
|
const candidates = [join33(here, "healthWorkerThread.js"), join33(here, "healthWorkerThread.ts")];
|
|
16174
16316
|
for (const candidate of candidates) {
|
|
16175
|
-
if (
|
|
16317
|
+
if (existsSync23(candidate)) return candidate;
|
|
16176
16318
|
}
|
|
16177
16319
|
return void 0;
|
|
16178
16320
|
}
|
|
@@ -16212,7 +16354,7 @@ import {
|
|
|
16212
16354
|
createReferenceBackgroundRecorder as createReferenceBackgroundRecorder2
|
|
16213
16355
|
} from "@hyperframes/engine";
|
|
16214
16356
|
import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframes/engine";
|
|
16215
|
-
import { extname as extname4, isAbsolute as isAbsolute4, join as join34, relative as relative4, sep as
|
|
16357
|
+
import { extname as extname4, isAbsolute as isAbsolute4, join as join34, relative as relative4, sep as sep4 } from "node:path";
|
|
16216
16358
|
import {
|
|
16217
16359
|
fpsToNumber as fpsToNumber9,
|
|
16218
16360
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
@@ -16459,6 +16601,7 @@ function assertDistributedOptions(value) {
|
|
|
16459
16601
|
"chunkSize",
|
|
16460
16602
|
"maxParallelChunks",
|
|
16461
16603
|
"targetChunkFrames",
|
|
16604
|
+
"minParallelChunks",
|
|
16462
16605
|
"planDirSizeLimitBytes"
|
|
16463
16606
|
]) {
|
|
16464
16607
|
assertOptionalInteger(value, field, 1);
|
|
@@ -16720,7 +16863,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
|
16720
16863
|
const { input, cleanupProjectDir } = prepared;
|
|
16721
16864
|
const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
|
|
16722
16865
|
const outputDir = dirname15(absoluteOutputPath);
|
|
16723
|
-
if (!
|
|
16866
|
+
if (!existsSync24(outputDir)) mkdirSync13(outputDir, { recursive: true });
|
|
16724
16867
|
return { input, cleanupProjectDir, absoluteOutputPath };
|
|
16725
16868
|
}
|
|
16726
16869
|
function validateRenderOverrides(body) {
|
|
@@ -16761,11 +16904,11 @@ function prepareProjectDirectory(projectDir, options) {
|
|
|
16761
16904
|
const candidate = nonEmptyString(projectDir);
|
|
16762
16905
|
if (!candidate) return null;
|
|
16763
16906
|
const absProjectDir = resolve10(candidate);
|
|
16764
|
-
if (!
|
|
16907
|
+
if (!existsSync24(absProjectDir) || !statSync11(absProjectDir).isDirectory()) {
|
|
16765
16908
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
16766
16909
|
}
|
|
16767
16910
|
const entry = options.entryFile || "index.html";
|
|
16768
|
-
if (!
|
|
16911
|
+
if (!existsSync24(resolve10(absProjectDir, entry))) {
|
|
16769
16912
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
16770
16913
|
}
|
|
16771
16914
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -16857,7 +17000,7 @@ function cleanupTempDir(dir, log) {
|
|
|
16857
17000
|
}
|
|
16858
17001
|
}
|
|
16859
17002
|
function outputFileSize(path) {
|
|
16860
|
-
return
|
|
17003
|
+
return existsSync24(path) ? statSync11(path).size : 0;
|
|
16861
17004
|
}
|
|
16862
17005
|
function createBlockingProgressReporter(log, requestId) {
|
|
16863
17006
|
let lastLoggedPct = -10;
|
|
@@ -17161,7 +17304,7 @@ function createRenderHandlers(options = {}) {
|
|
|
17161
17304
|
if (!artifact) {
|
|
17162
17305
|
return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
|
|
17163
17306
|
}
|
|
17164
|
-
if (!
|
|
17307
|
+
if (!existsSync24(artifact.path)) {
|
|
17165
17308
|
store.delete(token);
|
|
17166
17309
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
17167
17310
|
}
|