hyperframes 0.4.4 → 0.4.6

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 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.4.4" : "0.0.0-dev";
57
+ VERSION = true ? "0.4.6" : "0.0.0-dev";
58
58
  }
59
59
  });
60
60
 
@@ -6245,7 +6245,15 @@ function runSkillsAdd(repo) {
6245
6245
  return new Promise((resolve35, reject) => {
6246
6246
  const child = spawn("npx", ["skills", "add", repo, "--all"], {
6247
6247
  stdio: "inherit",
6248
- timeout: 12e4
6248
+ timeout: 12e4,
6249
+ // GH #316 — the upstream `skills` CLI shells out to `git clone`.
6250
+ // When Git's clone-hook protection is active (shipped on by
6251
+ // default in 2.45.1, reverted in 2.45.2, still present on many
6252
+ // corporate and CI setups), any globally-registered
6253
+ // `git lfs install` post-checkout hook aborts the clone. The
6254
+ // `repo` reaching this function is hardcoded in SOURCES below
6255
+ // — no user input reaches the spawn — so opting out here is safe.
6256
+ env: { ...process.env, GIT_CLONE_PROTECTION_ACTIVE: "0" }
6249
6257
  });
6250
6258
  child.on("close", (code, signal) => {
6251
6259
  if (code === 0) resolve35();
@@ -10507,10 +10515,10 @@ function compareDocumentPosition(nodeA, nodeB) {
10507
10515
  function uniqueSort(nodes) {
10508
10516
  nodes = nodes.filter((node, i2, arr) => !arr.includes(node, i2 + 1));
10509
10517
  nodes.sort((a, b) => {
10510
- const relative4 = compareDocumentPosition(a, b);
10511
- if (relative4 & DocumentPosition.PRECEDING) {
10518
+ const relative5 = compareDocumentPosition(a, b);
10519
+ if (relative5 & DocumentPosition.PRECEDING) {
10512
10520
  return -1;
10513
- } else if (relative4 & DocumentPosition.FOLLOWING) {
10521
+ } else if (relative5 & DocumentPosition.FOLLOWING) {
10514
10522
  return 1;
10515
10523
  }
10516
10524
  return 0;
@@ -20930,6 +20938,15 @@ function isFontResourceError(type, text, locationUrl) {
20930
20938
  `${locationUrl} ${text}`
20931
20939
  );
20932
20940
  }
20941
+ async function pollPageExpression(page, expression, timeoutMs, intervalMs = 100) {
20942
+ const deadline = Date.now() + timeoutMs;
20943
+ while (Date.now() < deadline) {
20944
+ const ready = Boolean(await page.evaluate(expression));
20945
+ if (ready) return true;
20946
+ await new Promise((resolve35) => setTimeout(resolve35, intervalMs));
20947
+ }
20948
+ return Boolean(await page.evaluate(expression));
20949
+ }
20933
20950
  async function initializeSession(session) {
20934
20951
  const { page, serverUrl } = session;
20935
20952
  page.on("console", (msg) => {
@@ -20959,14 +20976,26 @@ async function initializeSession(session) {
20959
20976
  if (session.captureMode === "screenshot") {
20960
20977
  await page.goto(url, { waitUntil: "domcontentloaded", timeout: 6e4 });
20961
20978
  const pageReadyTimeout2 = session.config?.playerReadyTimeout ?? DEFAULT_CONFIG2.playerReadyTimeout;
20962
- await page.waitForFunction(
20979
+ const pageReady2 = await pollPageExpression(
20980
+ page,
20963
20981
  `!!(window.__hf && typeof window.__hf.seek === "function" && window.__hf.duration > 0)`,
20964
- { timeout: pageReadyTimeout2 }
20982
+ pageReadyTimeout2
20965
20983
  );
20966
- await page.waitForFunction(
20984
+ if (!pageReady2) {
20985
+ throw new Error(
20986
+ `[FrameCapture] window.__hf not ready after ${pageReadyTimeout2}ms. Page must expose window.__hf = { duration, seek }.`
20987
+ );
20988
+ }
20989
+ const videosReady = await pollPageExpression(
20990
+ page,
20967
20991
  `document.querySelectorAll("video").length === 0 || Array.from(document.querySelectorAll("video")).every(v => v.readyState >= 1)`,
20968
- { timeout: pageReadyTimeout2 }
20992
+ pageReadyTimeout2
20969
20993
  );
20994
+ if (!videosReady) {
20995
+ throw new Error(
20996
+ `[FrameCapture] video metadata not ready after ${pageReadyTimeout2}ms. Video elements must load metadata before capture starts.`
20997
+ );
20998
+ }
20970
20999
  await page.evaluate(`document.fonts?.ready`);
20971
21000
  session.isInitialized = true;
20972
21001
  return;
@@ -24071,8 +24100,22 @@ function injectScriptsIntoHtml2(html, headScripts, bodyScripts, stripEmbedded) {
24071
24100
  }
24072
24101
  return html;
24073
24102
  }
24103
+ function injectScriptsAtHeadStart(html, scripts) {
24104
+ if (scripts.length === 0) return html;
24105
+ const headTags = scripts.map((src) => `<script>${src}</script>`).join("\n");
24106
+ if (html.includes("<head")) {
24107
+ return html.replace(/<head\b[^>]*>/i, (match) => `${match}
24108
+ ${headTags}`);
24109
+ }
24110
+ if (html.includes("<body")) {
24111
+ return html.replace("<body", () => `${headTags}
24112
+ <body`);
24113
+ }
24114
+ return headTags + "\n" + html;
24115
+ }
24074
24116
  function createFileServer2(options) {
24075
24117
  const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
24118
+ const preHeadScripts = options.preHeadScripts ?? [];
24076
24119
  const headScripts = options.headScripts ?? [getVerifiedHyperframeRuntimeSource()];
24077
24120
  const bodyScripts = options.bodyScripts ?? [RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT];
24078
24121
  const app = new Hono3();
@@ -24096,7 +24139,11 @@ function createFileServer2(options) {
24096
24139
  if (ext === ".html") {
24097
24140
  const rawHtml = readFileSync17(filePath, "utf-8");
24098
24141
  const isIndex = relativePath === "index.html";
24099
- const html = isIndex ? injectScriptsIntoHtml2(rawHtml, headScripts, bodyScripts, stripEmbeddedRuntime) : rawHtml;
24142
+ let html = rawHtml;
24143
+ if (preHeadScripts.length > 0) {
24144
+ html = injectScriptsAtHeadStart(html, preHeadScripts);
24145
+ }
24146
+ html = isIndex ? injectScriptsIntoHtml2(html, headScripts, bodyScripts, stripEmbeddedRuntime) : html;
24100
24147
  return c2.text(html, 200, { "Content-Type": contentType });
24101
24148
  }
24102
24149
  const content = readFileSync17(filePath);
@@ -24124,7 +24171,7 @@ function createFileServer2(options) {
24124
24171
  });
24125
24172
  });
24126
24173
  }
24127
- var MIME_TYPES3, RENDER_SEEK_MODE, RENDER_SEEK_DIAGNOSTICS, RENDER_SEEK_STEP, RENDER_SEEK_OFFSET_FRACTION, RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT;
24174
+ var MIME_TYPES3, VIRTUAL_TIME_SHIM, RENDER_SEEK_MODE, RENDER_SEEK_DIAGNOSTICS, RENDER_SEEK_STEP, RENDER_SEEK_OFFSET_FRACTION, RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT;
24128
24175
  var init_fileServer2 = __esm({
24129
24176
  "../producer/src/services/fileServer.ts"() {
24130
24177
  "use strict";
@@ -24151,6 +24198,102 @@ var init_fileServer2 = __esm({
24151
24198
  ".ttf": "font/ttf",
24152
24199
  ".otf": "font/otf"
24153
24200
  };
24201
+ VIRTUAL_TIME_SHIM = String.raw`(function() {
24202
+ if (window.__HF_VIRTUAL_TIME__) return;
24203
+
24204
+ var virtualNowMs = 0;
24205
+ var rafId = 1;
24206
+ var rafQueue = [];
24207
+ var OriginalDate = Date;
24208
+ var originalSetTimeout = window.setTimeout.bind(window);
24209
+ var originalClearTimeout = window.clearTimeout.bind(window);
24210
+ var originalSetInterval = window.setInterval.bind(window);
24211
+ var originalClearInterval = window.clearInterval.bind(window);
24212
+ var originalRequestAnimationFrame = window.requestAnimationFrame
24213
+ ? window.requestAnimationFrame.bind(window)
24214
+ : null;
24215
+ var originalCancelAnimationFrame = window.cancelAnimationFrame
24216
+ ? window.cancelAnimationFrame.bind(window)
24217
+ : null;
24218
+
24219
+ function flushAnimationFrame() {
24220
+ if (!rafQueue.length) return;
24221
+ var current = rafQueue.slice();
24222
+ rafQueue.length = 0;
24223
+ for (var i = 0; i < current.length; i++) {
24224
+ var entry = current[i];
24225
+ if (entry.cancelled) continue;
24226
+ try {
24227
+ entry.callback(virtualNowMs);
24228
+ } catch {}
24229
+ }
24230
+ }
24231
+
24232
+ function VirtualDate() {
24233
+ var args = Array.prototype.slice.call(arguments);
24234
+ if (!(this instanceof VirtualDate)) {
24235
+ return OriginalDate.apply(null, args.length ? args : [virtualNowMs]);
24236
+ }
24237
+ var instance = args.length ? new (Function.prototype.bind.apply(OriginalDate, [null].concat(args)))() : new OriginalDate(virtualNowMs);
24238
+ Object.setPrototypeOf(instance, VirtualDate.prototype);
24239
+ return instance;
24240
+ }
24241
+
24242
+ VirtualDate.prototype = OriginalDate.prototype;
24243
+ Object.setPrototypeOf(VirtualDate, OriginalDate);
24244
+ VirtualDate.now = function() { return virtualNowMs; };
24245
+ VirtualDate.parse = OriginalDate.parse.bind(OriginalDate);
24246
+ VirtualDate.UTC = OriginalDate.UTC.bind(OriginalDate);
24247
+
24248
+ try {
24249
+ Object.defineProperty(window, "Date", {
24250
+ configurable: true,
24251
+ writable: true,
24252
+ value: VirtualDate,
24253
+ });
24254
+ } catch {}
24255
+
24256
+ if (window.performance && typeof window.performance.now === "function") {
24257
+ try {
24258
+ Object.defineProperty(window.performance, "now", {
24259
+ configurable: true,
24260
+ value: function() { return virtualNowMs; },
24261
+ });
24262
+ } catch {}
24263
+ }
24264
+
24265
+ window.requestAnimationFrame = function(callback) {
24266
+ if (typeof callback !== "function") return 0;
24267
+ var entry = { id: rafId++, callback: callback, cancelled: false };
24268
+ rafQueue.push(entry);
24269
+ return entry.id;
24270
+ };
24271
+ window.cancelAnimationFrame = function(id) {
24272
+ for (var i = 0; i < rafQueue.length; i++) {
24273
+ if (rafQueue[i].id === id) {
24274
+ rafQueue[i].cancelled = true;
24275
+ }
24276
+ }
24277
+ };
24278
+
24279
+ window.__HF_VIRTUAL_TIME__ = {
24280
+ originalSetTimeout: originalSetTimeout,
24281
+ originalClearTimeout: originalClearTimeout,
24282
+ originalSetInterval: originalSetInterval,
24283
+ originalClearInterval: originalClearInterval,
24284
+ originalRequestAnimationFrame: originalRequestAnimationFrame,
24285
+ originalCancelAnimationFrame: originalCancelAnimationFrame,
24286
+ seekToTime: function(nextTimeMs) {
24287
+ var safeTimeMs = Math.max(0, Number(nextTimeMs) || 0);
24288
+ virtualNowMs = safeTimeMs;
24289
+ flushAnimationFrame();
24290
+ return virtualNowMs;
24291
+ },
24292
+ getTime: function() {
24293
+ return virtualNowMs;
24294
+ },
24295
+ };
24296
+ })();`;
24154
24297
  RENDER_SEEK_MODE = process.env.PRODUCER_RUNTIME_RENDER_SEEK_MODE === "strict-boundary" ? "strict-boundary" : "preview-phase";
24155
24298
  RENDER_SEEK_DIAGNOSTICS = process.env.PRODUCER_DEBUG_SEEK_DIAGNOSTICS === "true";
24156
24299
  RENDER_SEEK_STEP = Math.max(
@@ -24162,6 +24305,10 @@ var init_fileServer2 = __esm({
24162
24305
  Math.min(0.95, Number(process.env.PRODUCER_RUNTIME_RENDER_SEEK_OFFSET_FRACTION || 0.5))
24163
24306
  );
24164
24307
  RENDER_MODE_SCRIPT = `(function() {
24308
+ var __realSetTimeout =
24309
+ window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetTimeout === "function"
24310
+ ? window.__HF_VIRTUAL_TIME__.originalSetTimeout
24311
+ : window.setTimeout.bind(window);
24165
24312
  var __seekMode = ${JSON.stringify(RENDER_SEEK_MODE)};
24166
24313
  var __seekDiagnostics = ${RENDER_SEEK_DIAGNOSTICS ? "true" : "false"};
24167
24314
  var __seekStep = ${RENDER_SEEK_STEP};
@@ -24255,23 +24402,56 @@ var init_fileServer2 = __esm({
24255
24402
  window.__renderReady = true;
24256
24403
  return;
24257
24404
  }
24258
- setTimeout(waitForPlayer, 50);
24405
+ __realSetTimeout(waitForPlayer, 50);
24259
24406
  return;
24260
24407
  }
24261
24408
  if (installMediaFallbackPlayer()) {
24262
24409
  return;
24263
24410
  }
24264
- setTimeout(waitForPlayer, 50);
24411
+ __realSetTimeout(waitForPlayer, 50);
24265
24412
  }
24266
24413
  waitForPlayer();
24267
24414
  })();`;
24268
24415
  HF_BRIDGE_SCRIPT = `(function() {
24416
+ var __realSetInterval =
24417
+ window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetInterval === "function"
24418
+ ? window.__HF_VIRTUAL_TIME__.originalSetInterval
24419
+ : window.setInterval.bind(window);
24420
+ var __realClearInterval =
24421
+ window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalClearInterval === "function"
24422
+ ? window.__HF_VIRTUAL_TIME__.originalClearInterval
24423
+ : window.clearInterval.bind(window);
24269
24424
  function getDeclaredDuration() {
24270
24425
  var root = document.querySelector('[data-composition-id]');
24271
24426
  if (!root) return 0;
24272
24427
  var d = Number(root.getAttribute('data-duration'));
24273
24428
  return Number.isFinite(d) && d > 0 ? d : 0;
24274
24429
  }
24430
+ function seekSameOriginChildFrames(frameWindow, nextTimeMs) {
24431
+ var frames;
24432
+ try {
24433
+ frames = frameWindow.frames;
24434
+ } catch (_error) {
24435
+ return;
24436
+ }
24437
+ if (!frames || typeof frames.length !== "number") return;
24438
+ for (var i = 0; i < frames.length; i++) {
24439
+ var childWindow = null;
24440
+ try {
24441
+ childWindow = frames[i];
24442
+ if (!childWindow || childWindow === frameWindow) continue;
24443
+ if (
24444
+ childWindow.__HF_VIRTUAL_TIME__ &&
24445
+ typeof childWindow.__HF_VIRTUAL_TIME__.seekToTime === "function"
24446
+ ) {
24447
+ childWindow.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);
24448
+ }
24449
+ } catch (_error) {
24450
+ continue;
24451
+ }
24452
+ seekSameOriginChildFrames(childWindow, nextTimeMs);
24453
+ }
24454
+ }
24275
24455
  function bridge() {
24276
24456
  var p = window.__player;
24277
24457
  if (!p || typeof p.renderSeek !== "function" || typeof p.getDuration !== "function") {
@@ -24282,13 +24462,20 @@ var init_fileServer2 = __esm({
24282
24462
  var d = p.getDuration();
24283
24463
  return d > 0 ? d : getDeclaredDuration();
24284
24464
  },
24285
- seek: function(t) { p.renderSeek(t); },
24465
+ seek: function(t) {
24466
+ p.renderSeek(t);
24467
+ var nextTimeMs = (Math.max(0, Number(t) || 0)) * 1000;
24468
+ if (window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.seekToTime === "function") {
24469
+ window.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);
24470
+ }
24471
+ seekSameOriginChildFrames(window, nextTimeMs);
24472
+ },
24286
24473
  };
24287
24474
  return true;
24288
24475
  }
24289
24476
  if (bridge()) return;
24290
- var iv = setInterval(function() {
24291
- if (bridge()) clearInterval(iv);
24477
+ var iv = __realSetInterval(function() {
24478
+ if (bridge()) __realClearInterval(iv);
24292
24479
  }, 50);
24293
24480
  })();`;
24294
24481
  }
@@ -24302,6 +24489,40 @@ var init_ffprobe2 = __esm({
24302
24489
  }
24303
24490
  });
24304
24491
 
24492
+ // ../producer/src/utils/paths.ts
24493
+ import { resolve as resolve13, basename, join as join27, relative as relative2, isAbsolute as isAbsolute3 } from "path";
24494
+ function isPathInside(childPath, parentPath) {
24495
+ const absChild = resolve13(childPath);
24496
+ const absParent = resolve13(parentPath);
24497
+ if (absChild === absParent) return true;
24498
+ const rel = relative2(absParent, absChild);
24499
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute3(rel);
24500
+ }
24501
+ function toExternalAssetKey(absPath) {
24502
+ if (absPath.startsWith("hf-ext/")) return absPath;
24503
+ let normalised = absPath.replace(/\\/g, "/");
24504
+ normalised = normalised.replace(/^\/\/\?\/UNC\//i, "//");
24505
+ normalised = normalised.replace(/^\/\/\?\//, "");
24506
+ normalised = normalised.replace(/^\/\/([^/]+)\//, "unc/$1/");
24507
+ normalised = normalised.replace(/^\/+/, "");
24508
+ normalised = normalised.replace(/^([A-Za-z]):\/?/, "$1/");
24509
+ return "hf-ext/" + normalised;
24510
+ }
24511
+ function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS_DIR) {
24512
+ const absoluteProjectDir = resolve13(projectDir);
24513
+ const projectName = basename(absoluteProjectDir);
24514
+ const resolvedOutputPath = outputPath ?? join27(rendersDir, `${projectName}.mp4`);
24515
+ const absoluteOutputPath = resolve13(resolvedOutputPath);
24516
+ return { absoluteProjectDir, absoluteOutputPath };
24517
+ }
24518
+ var DEFAULT_RENDERS_DIR;
24519
+ var init_paths = __esm({
24520
+ "../producer/src/utils/paths.ts"() {
24521
+ "use strict";
24522
+ DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? resolve13(new URL(import.meta.url).pathname, "../../..", "renders");
24523
+ }
24524
+ });
24525
+
24305
24526
  // ../producer/src/utils/urlDownloader.ts
24306
24527
  var init_urlDownloader2 = __esm({
24307
24528
  "../producer/src/utils/urlDownloader.ts"() {
@@ -24365,7 +24586,7 @@ var init_fontData_generated = __esm({
24365
24586
  // ../producer/src/services/deterministicFonts.ts
24366
24587
  import { existsSync as existsSync25, mkdirSync as mkdirSync15, readFileSync as readFileSync18, writeFileSync as writeFileSync9 } from "fs";
24367
24588
  import { homedir as homedir6 } from "os";
24368
- import { join as join27 } from "path";
24589
+ import { join as join28 } from "path";
24369
24590
  function normalizeFamilyName(family) {
24370
24591
  return family.trim().replace(/^['"]|['"]$/g, "").trim().toLowerCase();
24371
24592
  }
@@ -24476,14 +24697,14 @@ function fontSlug(familyName) {
24476
24697
  return familyName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
24477
24698
  }
24478
24699
  function fontCacheDir(slug) {
24479
- const dir = join27(GOOGLE_FONTS_CACHE_DIR, slug);
24700
+ const dir = join28(GOOGLE_FONTS_CACHE_DIR, slug);
24480
24701
  if (!existsSync25(dir)) {
24481
24702
  mkdirSync15(dir, { recursive: true });
24482
24703
  }
24483
24704
  return dir;
24484
24705
  }
24485
24706
  function cachedWoff2Path(slug, weight, style) {
24486
- return join27(fontCacheDir(slug), `${weight}-${style}.woff2`);
24707
+ return join28(fontCacheDir(slug), `${weight}-${style}.woff2`);
24487
24708
  }
24488
24709
  async function fetchGoogleFont(familyName) {
24489
24710
  const slug = fontSlug(familyName);
@@ -24695,14 +24916,14 @@ var init_deterministicFonts = __esm({
24695
24916
  poppins: "poppins",
24696
24917
  "segoe ui": "roboto"
24697
24918
  };
24698
- GOOGLE_FONTS_CACHE_DIR = join27(homedir6(), ".cache", "hyperframes", "fonts");
24919
+ GOOGLE_FONTS_CACHE_DIR = join28(homedir6(), ".cache", "hyperframes", "fonts");
24699
24920
  WOFF2_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
24700
24921
  }
24701
24922
  });
24702
24923
 
24703
24924
  // ../producer/src/services/htmlCompiler.ts
24704
24925
  import { readFileSync as readFileSync19, existsSync as existsSync26, mkdirSync as mkdirSync16 } from "fs";
24705
- import { join as join28, dirname as dirname9, resolve as resolve13 } from "path";
24926
+ import { join as join29, dirname as dirname9, resolve as resolve14 } from "path";
24706
24927
  import postcss from "postcss";
24707
24928
  function dedupeElementsById(elements) {
24708
24929
  const deduped = /* @__PURE__ */ new Map();
@@ -24711,6 +24932,36 @@ function dedupeElementsById(elements) {
24711
24932
  }
24712
24933
  return Array.from(deduped.values());
24713
24934
  }
24935
+ function stripJsComments(source) {
24936
+ return source.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
24937
+ }
24938
+ function detectRenderModeHints(html) {
24939
+ const reasons = [];
24940
+ const { document: document2 } = parseHTML(html);
24941
+ if (document2.querySelector("iframe")) {
24942
+ reasons.push({
24943
+ code: "iframe",
24944
+ message: "Detected <iframe> in the composition DOM. Nested iframe animation is routed through screenshot capture mode for compatibility."
24945
+ });
24946
+ }
24947
+ let scriptMatch;
24948
+ const scriptPattern = new RegExp(INLINE_SCRIPT_PATTERN.source, INLINE_SCRIPT_PATTERN.flags);
24949
+ while ((scriptMatch = scriptPattern.exec(html)) !== null) {
24950
+ const attrs = scriptMatch[1] || "";
24951
+ if (/\bsrc\s*=/i.test(attrs)) continue;
24952
+ const content = stripJsComments(scriptMatch[2] || "");
24953
+ if (!/requestAnimationFrame\s*\(/.test(content)) continue;
24954
+ reasons.push({
24955
+ code: "requestAnimationFrame",
24956
+ message: "Detected raw requestAnimationFrame() in an inline script. This render is routed through screenshot capture mode with virtual time enabled."
24957
+ });
24958
+ break;
24959
+ }
24960
+ return {
24961
+ recommendScreenshot: reasons.length > 0,
24962
+ reasons
24963
+ };
24964
+ }
24714
24965
  async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagName19) {
24715
24966
  let filePath = src;
24716
24967
  if (isHttpUrl(src)) {
@@ -24721,7 +24972,7 @@ async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagNa
24721
24972
  return { duration: 0, resolvedPath: src };
24722
24973
  }
24723
24974
  } else if (!filePath.startsWith("/")) {
24724
- filePath = join28(baseDir, filePath);
24975
+ filePath = join29(baseDir, filePath);
24725
24976
  }
24726
24977
  if (!existsSync26(filePath)) {
24727
24978
  return { duration: 0, resolvedPath: filePath };
@@ -24787,7 +25038,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, parentOffset
24787
25038
  const elEnd = elEndRaw ? parseFloat(elEndRaw) : Infinity;
24788
25039
  const absoluteStart = parentOffset + elStart;
24789
25040
  const absoluteEnd = Math.min(parentEnd, isFinite(elEnd) ? parentOffset + elEnd : Infinity);
24790
- const filePath = resolve13(projectDir, srcPath);
25041
+ const filePath = resolve14(projectDir, srcPath);
24791
25042
  if (visited.has(filePath)) {
24792
25043
  continue;
24793
25044
  }
@@ -24987,7 +25238,7 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
24987
25238
  if (!srcPath) continue;
24988
25239
  let compHtml = subCompositions.get(srcPath) || null;
24989
25240
  if (!compHtml) {
24990
- const filePath = resolve13(projectDir, srcPath);
25241
+ const filePath = resolve14(projectDir, srcPath);
24991
25242
  if (existsSync26(filePath)) {
24992
25243
  compHtml = readFileSync19(filePath, "utf-8");
24993
25244
  }
@@ -25200,7 +25451,7 @@ ${safeText}
25200
25451
  return result;
25201
25452
  }
25202
25453
  function collectExternalAssets(html, projectDir) {
25203
- const absProjectDir = resolve13(projectDir);
25454
+ const absProjectDir = resolve14(projectDir);
25204
25455
  const externalAssets = /* @__PURE__ */ new Map();
25205
25456
  const CSS_URL_RE2 = /\burl\(\s*(["']?)([^)"']+)\1\s*\)/g;
25206
25457
  function processPath(rawPath) {
@@ -25208,12 +25459,12 @@ function collectExternalAssets(html, projectDir) {
25208
25459
  if (!trimmed || trimmed.startsWith("/") || trimmed.startsWith("http://") || trimmed.startsWith("https://") || trimmed.startsWith("//") || trimmed.startsWith("data:") || trimmed.startsWith("#")) {
25209
25460
  return null;
25210
25461
  }
25211
- const absPath = resolve13(absProjectDir, trimmed);
25212
- if (absPath.startsWith(absProjectDir + "/") || absPath === absProjectDir) {
25462
+ const absPath = resolve14(absProjectDir, trimmed);
25463
+ if (isPathInside(absPath, absProjectDir)) {
25213
25464
  return null;
25214
25465
  }
25215
25466
  if (!existsSync26(absPath)) return null;
25216
- const safeKey = "hf-ext/" + absPath.replace(/^\//, "");
25467
+ const safeKey = toExternalAssetKey(absPath);
25217
25468
  externalAssets.set(safeKey, absPath);
25218
25469
  return safeKey;
25219
25470
  }
@@ -25274,6 +25525,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
25274
25525
  /(<(?:video|audio)\b[^>]*?)\s+preload\s*=\s*["']none["']/gi,
25275
25526
  "$1"
25276
25527
  );
25528
+ const renderModeHints = detectRenderModeHints(sanitizedHtml);
25277
25529
  const coalescedHtml = await injectDeterministicFontFaces(
25278
25530
  coalesceHeadStylesAndBodyScripts2(promoteCssImportsToLinkTags(sanitizedHtml))
25279
25531
  );
@@ -25285,7 +25537,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
25285
25537
  const audios = dedupeElementsById([...mainAudios, ...subAudios]);
25286
25538
  for (const video of videos) {
25287
25539
  if (isHttpUrl(video.src)) continue;
25288
- const videoPath = resolve13(projectDir, video.src);
25540
+ const videoPath = resolve14(projectDir, video.src);
25289
25541
  const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
25290
25542
  Promise.all([analyzeKeyframeIntervals(videoPath), extractVideoMetadata(videoPath)]).then(([analysis, metadata]) => {
25291
25543
  if (analysis.isProblematic) {
@@ -25317,7 +25569,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
25317
25569
  externalAssets,
25318
25570
  width,
25319
25571
  height,
25320
- staticDuration
25572
+ staticDuration,
25573
+ renderModeHints
25321
25574
  };
25322
25575
  }
25323
25576
  async function discoverMediaFromBrowser(page) {
@@ -25411,18 +25664,22 @@ async function recompileWithResolutions(compiled, resolutions, projectDir, downl
25411
25664
  subCompositions,
25412
25665
  videos,
25413
25666
  audios,
25414
- unresolvedCompositions: remaining
25667
+ unresolvedCompositions: remaining,
25668
+ renderModeHints: compiled.renderModeHints
25415
25669
  };
25416
25670
  }
25671
+ var INLINE_SCRIPT_PATTERN;
25417
25672
  var init_htmlCompiler2 = __esm({
25418
25673
  "../producer/src/services/htmlCompiler.ts"() {
25419
25674
  "use strict";
25420
25675
  init_esm10();
25421
25676
  init_src();
25422
25677
  init_ffprobe2();
25678
+ init_paths();
25423
25679
  init_src2();
25424
25680
  init_urlDownloader2();
25425
25681
  init_deterministicFonts();
25682
+ INLINE_SCRIPT_PATTERN = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
25426
25683
  }
25427
25684
  });
25428
25685
 
@@ -25478,7 +25735,7 @@ import {
25478
25735
  copyFileSync as copyFileSync2,
25479
25736
  appendFileSync
25480
25737
  } from "fs";
25481
- import { join as join29, dirname as dirname10, resolve as resolve14 } from "path";
25738
+ import { join as join30, dirname as dirname10, resolve as resolve15 } from "path";
25482
25739
  import { randomUUID as randomUUID2 } from "crypto";
25483
25740
  import { freemem as freemem2 } from "os";
25484
25741
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -25534,17 +25791,17 @@ function installDebugLogger(logPath, log = defaultLogger) {
25534
25791
  };
25535
25792
  }
25536
25793
  function writeCompiledArtifacts(compiled, workDir, includeSummary) {
25537
- const compileDir = join29(workDir, "compiled");
25794
+ const compileDir = join30(workDir, "compiled");
25538
25795
  mkdirSync17(compileDir, { recursive: true });
25539
- writeFileSync10(join29(compileDir, "index.html"), compiled.html, "utf-8");
25796
+ writeFileSync10(join30(compileDir, "index.html"), compiled.html, "utf-8");
25540
25797
  for (const [srcPath, html] of compiled.subCompositions) {
25541
- const outPath = join29(compileDir, srcPath);
25798
+ const outPath = join30(compileDir, srcPath);
25542
25799
  mkdirSync17(dirname10(outPath), { recursive: true });
25543
25800
  writeFileSync10(outPath, html, "utf-8");
25544
25801
  }
25545
25802
  for (const [relativePath, absolutePath] of compiled.externalAssets) {
25546
- const outPath = resolve14(join29(compileDir, relativePath));
25547
- if (!outPath.startsWith(compileDir + "/")) {
25803
+ const outPath = resolve15(join30(compileDir, relativePath));
25804
+ if (!isPathInside(outPath, compileDir)) {
25548
25805
  console.warn(`[Render] Skipping external asset with unsafe path: ${relativePath}`);
25549
25806
  continue;
25550
25807
  }
@@ -25570,11 +25827,20 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
25570
25827
  end: a.end,
25571
25828
  mediaStart: a.mediaStart
25572
25829
  })),
25573
- subCompositions: Array.from(compiled.subCompositions.keys())
25830
+ subCompositions: Array.from(compiled.subCompositions.keys()),
25831
+ renderModeHints: compiled.renderModeHints
25574
25832
  };
25575
- writeFileSync10(join29(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
25833
+ writeFileSync10(join30(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
25576
25834
  }
25577
25835
  }
25836
+ function applyRenderModeHints(cfg, compiled, log = defaultLogger) {
25837
+ if (cfg.forceScreenshot || !compiled.renderModeHints.recommendScreenshot) return;
25838
+ cfg.forceScreenshot = true;
25839
+ log.warn("Auto-selected screenshot capture mode for render compatibility", {
25840
+ reasonCodes: compiled.renderModeHints.reasons.map((reason) => reason.code),
25841
+ reasons: compiled.renderModeHints.reasons.map((reason) => reason.message)
25842
+ });
25843
+ }
25578
25844
  function createRenderJob(config) {
25579
25845
  return {
25580
25846
  id: randomUUID2(),
@@ -25616,9 +25882,9 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
25616
25882
  }
25617
25883
  async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
25618
25884
  const moduleDir = dirname10(fileURLToPath3(import.meta.url));
25619
- const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve14(process.env.PRODUCER_RENDERS_DIR, "..") : resolve14(moduleDir, "../..");
25620
- const debugDir = join29(producerRoot, ".debug");
25621
- const workDir = job.config.debug ? join29(debugDir, job.id) : join29(dirname10(outputPath), `work-${job.id}`);
25885
+ const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve15(process.env.PRODUCER_RENDERS_DIR, "..") : resolve15(moduleDir, "../..");
25886
+ const debugDir = join30(producerRoot, ".debug");
25887
+ const workDir = job.config.debug ? join30(debugDir, job.id) : join30(dirname10(outputPath), `work-${job.id}`);
25622
25888
  const pipelineStart = Date.now();
25623
25889
  const log = job.config.logger ?? defaultLogger;
25624
25890
  let fileServer = null;
@@ -25626,7 +25892,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25626
25892
  let lastBrowserConsole = [];
25627
25893
  let restoreLogger = null;
25628
25894
  const perfStages = {};
25629
- const perfOutputPath = join29(workDir, "perf-summary.json");
25895
+ const perfOutputPath = join30(workDir, "perf-summary.json");
25630
25896
  const cfg = { ...job.config.producerConfig ?? resolveConfig() };
25631
25897
  const outputFormat = job.config.format ?? "mp4";
25632
25898
  const isWebm = outputFormat === "webm";
@@ -25648,19 +25914,19 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25648
25914
  assertNotAborted();
25649
25915
  if (!existsSync27(workDir)) mkdirSync17(workDir, { recursive: true });
25650
25916
  if (job.config.debug) {
25651
- const logPath = join29(workDir, "render.log");
25917
+ const logPath = join30(workDir, "render.log");
25652
25918
  restoreLogger = installDebugLogger(logPath, log);
25653
25919
  }
25654
25920
  const entryFile = job.config.entryFile || "index.html";
25655
- let htmlPath = join29(projectDir, entryFile);
25921
+ let htmlPath = join30(projectDir, entryFile);
25656
25922
  if (!existsSync27(htmlPath)) {
25657
25923
  throw new Error(`Entry file not found: ${htmlPath}`);
25658
25924
  }
25659
25925
  assertNotAborted();
25660
25926
  const rawEntry = readFileSync20(htmlPath, "utf-8");
25661
25927
  if (entryFile !== "index.html" && rawEntry.trimStart().startsWith("<template")) {
25662
- const wrapperPath = join29(workDir, "standalone-entry.html");
25663
- const projectIndexPath = join29(projectDir, "index.html");
25928
+ const wrapperPath = join30(workDir, "standalone-entry.html");
25929
+ const projectIndexPath = join30(projectDir, "index.html");
25664
25930
  if (!existsSync27(projectIndexPath)) {
25665
25931
  throw new Error(
25666
25932
  `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
@@ -25684,9 +25950,10 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25684
25950
  const stage1Start = Date.now();
25685
25951
  updateJobStatus(job, "preprocessing", "Compiling composition", 5, onProgress);
25686
25952
  const compileStart = Date.now();
25687
- let compiled = await compileForRender(projectDir, htmlPath, join29(workDir, "downloads"));
25953
+ let compiled = await compileForRender(projectDir, htmlPath, join30(workDir, "downloads"));
25688
25954
  assertNotAborted();
25689
25955
  perfStages.compileOnlyMs = Date.now() - compileStart;
25956
+ applyRenderModeHints(cfg, compiled, log);
25690
25957
  writeCompiledArtifacts(compiled, workDir, Boolean(job.config.debug));
25691
25958
  log.info("Compiled composition metadata", {
25692
25959
  entryFile,
@@ -25694,7 +25961,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25694
25961
  width: compiled.width,
25695
25962
  height: compiled.height,
25696
25963
  videoCount: compiled.videos.length,
25697
- audioCount: compiled.audios.length
25964
+ audioCount: compiled.audios.length,
25965
+ renderModeHints: compiled.renderModeHints
25698
25966
  });
25699
25967
  const composition = {
25700
25968
  duration: compiled.staticDuration,
@@ -25713,8 +25981,9 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25713
25981
  reasons.push(`${compiled.unresolvedCompositions.length} unresolved composition(s)`);
25714
25982
  fileServer = await createFileServer2({
25715
25983
  projectDir,
25716
- compiledDir: join29(workDir, "compiled"),
25717
- port: 0
25984
+ compiledDir: join30(workDir, "compiled"),
25985
+ port: 0,
25986
+ preHeadScripts: [VIRTUAL_TIME_SHIM]
25718
25987
  });
25719
25988
  assertNotAborted();
25720
25989
  const captureOpts = {
@@ -25726,7 +25995,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25726
25995
  };
25727
25996
  probeSession = await createCaptureSession(
25728
25997
  fileServer.url,
25729
- join29(workDir, "probe"),
25998
+ join30(workDir, "probe"),
25730
25999
  captureOpts,
25731
26000
  null,
25732
26001
  cfg
@@ -25758,7 +26027,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25758
26027
  compiled,
25759
26028
  resolutions,
25760
26029
  projectDir,
25761
- join29(workDir, "downloads")
26030
+ join30(workDir, "downloads")
25762
26031
  );
25763
26032
  assertNotAborted();
25764
26033
  composition.videos = compiled.videos;
@@ -25893,12 +26162,12 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25893
26162
  const stage2Start = Date.now();
25894
26163
  updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress);
25895
26164
  let frameLookup = null;
25896
- const compiledDir = join29(workDir, "compiled");
26165
+ const compiledDir = join30(workDir, "compiled");
25897
26166
  if (composition.videos.length > 0) {
25898
26167
  const extractionResult = await extractAllVideoFrames(
25899
26168
  composition.videos,
25900
26169
  projectDir,
25901
- { fps: job.config.fps, outputDir: join29(workDir, "video-frames") },
26170
+ { fps: job.config.fps, outputDir: join30(workDir, "video-frames") },
25902
26171
  abortSignal,
25903
26172
  void 0,
25904
26173
  compiledDir
@@ -25932,13 +26201,13 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25932
26201
  }
25933
26202
  const stage3Start = Date.now();
25934
26203
  updateJobStatus(job, "preprocessing", "Processing audio tracks", 20, onProgress);
25935
- const audioOutputPath = join29(workDir, "audio.aac");
26204
+ const audioOutputPath = join30(workDir, "audio.aac");
25936
26205
  let hasAudio = false;
25937
26206
  if (composition.audios.length > 0) {
25938
26207
  const audioResult = await processCompositionAudio(
25939
26208
  composition.audios,
25940
26209
  projectDir,
25941
- join29(workDir, "audio-work"),
26210
+ join30(workDir, "audio-work"),
25942
26211
  audioOutputPath,
25943
26212
  job.duration,
25944
26213
  abortSignal,
@@ -25956,12 +26225,13 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25956
26225
  if (!fileServer) {
25957
26226
  fileServer = await createFileServer2({
25958
26227
  projectDir,
25959
- compiledDir: join29(workDir, "compiled"),
25960
- port: 0
26228
+ compiledDir: join30(workDir, "compiled"),
26229
+ port: 0,
26230
+ preHeadScripts: [VIRTUAL_TIME_SHIM]
25961
26231
  });
25962
26232
  assertNotAborted();
25963
26233
  }
25964
- const framesDir = join29(workDir, "captured-frames");
26234
+ const framesDir = join30(workDir, "captured-frames");
25965
26235
  if (!existsSync27(framesDir)) mkdirSync17(framesDir, { recursive: true });
25966
26236
  const captureOptions = {
25967
26237
  width,
@@ -25973,7 +26243,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
25973
26243
  const workerCount = calculateOptimalWorkers(job.totalFrames, job.config.workers, cfg);
25974
26244
  const FORMAT_EXT2 = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
25975
26245
  const videoExt = FORMAT_EXT2[outputFormat] ?? ".mp4";
25976
- const videoOnlyPath = join29(workDir, `video-only${videoExt}`);
26246
+ const videoOnlyPath = join30(workDir, `video-only${videoExt}`);
25977
26247
  const preset = getEncoderPreset(job.config.quality, outputFormat);
25978
26248
  const effectiveQuality = job.config.crf ?? preset.quality;
25979
26249
  const effectiveBitrate = job.config.videoBitrate;
@@ -26249,7 +26519,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
26249
26519
  }
26250
26520
  if (job.config.debug) {
26251
26521
  if (existsSync27(outputPath)) {
26252
- const debugOutput = join29(workDir, `output${videoExt}`);
26522
+ const debugOutput = join30(workDir, `output${videoExt}`);
26253
26523
  copyFileSync2(outputPath, debugOutput);
26254
26524
  }
26255
26525
  } else {
@@ -26350,6 +26620,7 @@ var init_renderOrchestrator = __esm({
26350
26620
  init_fileServer2();
26351
26621
  init_htmlCompiler2();
26352
26622
  init_logger();
26623
+ init_paths();
26353
26624
  RenderCancelledError = class extends Error {
26354
26625
  reason;
26355
26626
  constructor(message = "render_cancelled", reason = "aborted") {
@@ -26387,7 +26658,7 @@ var init_config3 = __esm({
26387
26658
 
26388
26659
  // ../producer/src/services/hyperframeLint.ts
26389
26660
  import { existsSync as existsSync28, readFileSync as readFileSync21, statSync as statSync8 } from "fs";
26390
- import { resolve as resolve15, join as join30 } from "path";
26661
+ import { resolve as resolve16, join as join31 } from "path";
26391
26662
  function isStringRecord(value) {
26392
26663
  if (!value || typeof value !== "object" || Array.isArray(value)) {
26393
26664
  return false;
@@ -26414,7 +26685,7 @@ function pickEntryFile(files, preferredEntryFile) {
26414
26685
  return null;
26415
26686
  }
26416
26687
  function readProjectEntryFile(projectDir, preferredEntryFile) {
26417
- const absProjectDir = resolve15(projectDir);
26688
+ const absProjectDir = resolve16(projectDir);
26418
26689
  if (!existsSync28(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
26419
26690
  return { error: `Project directory not found: ${absProjectDir}` };
26420
26691
  }
@@ -26422,7 +26693,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
26422
26693
  (value) => typeof value === "string" && value.trim().length > 0
26423
26694
  );
26424
26695
  for (const entryFile of entryCandidates) {
26425
- const absoluteEntryPath = resolve15(absProjectDir, entryFile);
26696
+ const absoluteEntryPath = resolve16(absProjectDir, entryFile);
26426
26697
  if (!absoluteEntryPath.startsWith(absProjectDir)) {
26427
26698
  return { error: `Entry file must stay inside project directory: ${entryFile}` };
26428
26699
  }
@@ -26435,7 +26706,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
26435
26706
  }
26436
26707
  }
26437
26708
  return {
26438
- error: `No HTML entry file found in project directory: ${join30(absProjectDir, preferredEntryFile || "index.html")}`
26709
+ error: `No HTML entry file found in project directory: ${join31(absProjectDir, preferredEntryFile || "index.html")}`
26439
26710
  };
26440
26711
  }
26441
26712
  function prepareHyperframeLintBody(body) {
@@ -26481,23 +26752,6 @@ var init_hyperframeLint = __esm({
26481
26752
  }
26482
26753
  });
26483
26754
 
26484
- // ../producer/src/utils/paths.ts
26485
- import { resolve as resolve16, basename, join as join31 } from "path";
26486
- function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS_DIR) {
26487
- const absoluteProjectDir = resolve16(projectDir);
26488
- const projectName = basename(absoluteProjectDir);
26489
- const resolvedOutputPath = outputPath ?? join31(rendersDir, `${projectName}.mp4`);
26490
- const absoluteOutputPath = resolve16(resolvedOutputPath);
26491
- return { absoluteProjectDir, absoluteOutputPath };
26492
- }
26493
- var DEFAULT_RENDERS_DIR;
26494
- var init_paths = __esm({
26495
- "../producer/src/utils/paths.ts"() {
26496
- "use strict";
26497
- DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? resolve16(new URL(import.meta.url).pathname, "../../..", "renders");
26498
- }
26499
- });
26500
-
26501
26755
  // ../producer/src/utils/semaphore.ts
26502
26756
  var Semaphore;
26503
26757
  var init_semaphore = __esm({
@@ -28300,7 +28554,7 @@ __export(add_exports, {
28300
28554
  runAdd: () => runAdd
28301
28555
  });
28302
28556
  import { existsSync as existsSync33 } from "fs";
28303
- import { resolve as resolve21, relative as relative2 } from "path";
28557
+ import { resolve as resolve21, relative as relative3 } from "path";
28304
28558
  function remapTarget(item, originalTarget, paths) {
28305
28559
  if (item.type === "hyperframes:block") {
28306
28560
  const blocksDir = paths.blocks.replace(/\/+$/, "");
@@ -28439,7 +28693,7 @@ var init_add = __esm({
28439
28693
  console.log("");
28440
28694
  console.log(`${c.success("\u2713")} Added ${c.accent(result.name)} (${result.type})`);
28441
28695
  for (const file of result.written) {
28442
- console.log(` ${c.dim(relative2(projectDir, file))}`);
28696
+ console.log(` ${c.dim(relative3(projectDir, file))}`);
28443
28697
  }
28444
28698
  if (result.snippet) {
28445
28699
  console.log("");
@@ -30868,18 +31122,18 @@ function checkFFmpeg() {
30868
31122
  return {
30869
31123
  ok: false,
30870
31124
  detail: "Not found",
30871
- hint: process.platform === "darwin" ? "brew install ffmpeg" : "sudo apt install ffmpeg"
31125
+ hint: getFFmpegInstallHint()
30872
31126
  };
30873
31127
  }
30874
31128
  function checkFFprobe() {
30875
31129
  try {
30876
- const result = execSync3("which ffprobe", { encoding: "utf-8", timeout: 5e3 }).trim();
30877
- return { ok: true, detail: result };
31130
+ const version = execSync3("ffprobe -version", { encoding: "utf-8", timeout: 5e3 }).split("\n")[0] ?? "";
31131
+ return { ok: true, detail: version.trim() };
30878
31132
  } catch {
30879
31133
  return {
30880
31134
  ok: false,
30881
31135
  detail: "Not found",
30882
- hint: "Installed with ffmpeg"
31136
+ hint: `Installed with ffmpeg \u2014 ${getFFmpegInstallHint()}`
30883
31137
  };
30884
31138
  }
30885
31139
  }
@@ -31531,7 +31785,7 @@ __export(snapshot_exports, {
31531
31785
  examples: () => examples18
31532
31786
  });
31533
31787
  import { existsSync as existsSync44, readFileSync as readFileSync31, mkdirSync as mkdirSync24 } from "fs";
31534
- import { resolve as resolve32, join as join44, dirname as dirname20, relative as relative3, isAbsolute as isAbsolute3 } from "path";
31788
+ import { resolve as resolve32, join as join44, dirname as dirname20, relative as relative4, isAbsolute as isAbsolute4 } from "path";
31535
31789
  import { fileURLToPath as fileURLToPath8 } from "url";
31536
31790
  async function captureSnapshots(projectDir, opts) {
31537
31791
  const { bundleToSingleHtml: bundleToSingleHtml2 } = await Promise.resolve().then(() => (init_compiler(), compiler_exports));
@@ -31564,8 +31818,8 @@ async function captureSnapshots(projectDir, opts) {
31564
31818
  return;
31565
31819
  }
31566
31820
  const filePath = resolve32(projectDir, decodeURIComponent(url).replace(/^\//, ""));
31567
- const rel = relative3(projectDir, filePath);
31568
- if (rel.startsWith("..") || isAbsolute3(rel)) {
31821
+ const rel = relative4(projectDir, filePath);
31822
+ if (rel.startsWith("..") || isAbsolute4(rel)) {
31569
31823
  res.writeHead(403);
31570
31824
  res.end();
31571
31825
  return;