hyperframes 0.6.65 → 0.6.67

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
@@ -50,7 +50,7 @@ var VERSION;
50
50
  var init_version = __esm({
51
51
  "src/version.ts"() {
52
52
  "use strict";
53
- VERSION = true ? "0.6.65" : "0.0.0-dev";
53
+ VERSION = true ? "0.6.67" : "0.0.0-dev";
54
54
  }
55
55
  });
56
56
 
@@ -58212,7 +58212,7 @@ function isSafeAttributeValue(name, value) {
58212
58212
  function patchElementInHtml(source, target, operations) {
58213
58213
  const { document: document2, wrappedFragment } = parseSourceDocument(source);
58214
58214
  const el = findTargetElement(document2, target);
58215
- if (!el || !isHTMLElement(el)) return source;
58215
+ if (!el || !isHTMLElement(el)) return { html: source, matched: false };
58216
58216
  const htmlEl = el;
58217
58217
  for (const op of operations) {
58218
58218
  switch (op.type) {
@@ -58247,7 +58247,10 @@ function patchElementInHtml(source, target, operations) {
58247
58247
  break;
58248
58248
  }
58249
58249
  }
58250
- return wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
58250
+ return {
58251
+ html: wrappedFragment ? document2.body.innerHTML || "" : document2.toString(),
58252
+ matched: true
58253
+ };
58251
58254
  }
58252
58255
  function probeElementInSource(source, target) {
58253
58256
  if (!target.id && !target.selector) return false;
@@ -58531,12 +58534,16 @@ function registerFileRoutes(api, adapter2) {
58531
58534
  } catch {
58532
58535
  return c3.json({ error: "not found" }, 404);
58533
58536
  }
58534
- return writeIfChanged(
58535
- c3,
58536
- ctx.absPath,
58537
+ const { html: patched, matched } = patchElementInHtml(
58537
58538
  originalContent,
58538
- patchElementInHtml(originalContent, parsed.target, parsed.body.operations)
58539
+ parsed.target,
58540
+ parsed.body.operations
58539
58541
  );
58542
+ if (patched === originalContent) {
58543
+ return c3.json({ ok: true, changed: false, matched, content: originalContent });
58544
+ }
58545
+ writeFileSync8(ctx.absPath, patched, "utf-8");
58546
+ return c3.json({ ok: true, changed: true, matched, content: patched });
58540
58547
  });
58541
58548
  api.post("/projects/:id/file-mutations/probe-element/*", async (c3) => {
58542
58549
  const ctx = await resolveFileMutationContext(c3, adapter2, "probe-element");
@@ -73083,7 +73090,8 @@ async function runCaptureStreamingStage(input2) {
73083
73090
  streamingEncoder = await spawnStreamingEncoder(
73084
73091
  videoOnlyPath,
73085
73092
  streamingEncoderOptions,
73086
- abortSignal
73093
+ abortSignal,
73094
+ cfg
73087
73095
  );
73088
73096
  assertNotAborted();
73089
73097
  } catch (err) {
@@ -76202,9 +76210,72 @@ function parseRenderOptions(body) {
76202
76210
  const outputPath = typeof body.outputPath === "string" && body.outputPath.trim().length > 0 ? body.outputPath : typeof body.output === "string" && body.output.trim().length > 0 ? body.output : null;
76203
76211
  const entryFile = typeof body.entryFile === "string" && body.entryFile.trim().length > 0 ? body.entryFile.trim() : void 0;
76204
76212
  const format = ["mp4", "webm", "mov"].includes(body.format) ? body.format : void 0;
76205
- return { outputPath, fps, quality, workers, useGpu, debug, entryFile, format };
76213
+ const { variables, outputResolution } = parseRenderOverrides(body);
76214
+ return {
76215
+ outputPath,
76216
+ fps,
76217
+ quality,
76218
+ workers,
76219
+ useGpu,
76220
+ debug,
76221
+ entryFile,
76222
+ format,
76223
+ variables,
76224
+ outputResolution
76225
+ };
76226
+ }
76227
+ function isPlainObject(value) {
76228
+ return typeof value === "object" && value !== null && !Array.isArray(value);
76229
+ }
76230
+ function parseRenderOverrides(body) {
76231
+ const variables = isPlainObject(body.variables) ? body.variables : void 0;
76232
+ const outputResolution = typeof body.outputResolution === "string" ? normalizeResolutionFlag(body.outputResolution) : void 0;
76233
+ return { variables, outputResolution };
76234
+ }
76235
+ function buildRenderJobConfig(input2, log2) {
76236
+ return {
76237
+ fps: input2.fps,
76238
+ quality: input2.quality,
76239
+ format: input2.format,
76240
+ workers: input2.workers,
76241
+ useGpu: input2.useGpu,
76242
+ debug: input2.debug,
76243
+ entryFile: input2.entryFile,
76244
+ variables: input2.variables,
76245
+ outputResolution: input2.outputResolution,
76246
+ logger: log2
76247
+ };
76248
+ }
76249
+ function resolvePreparedRenderOutput(prepared, rendersDir, log2) {
76250
+ const { input: input2, cleanupProjectDir } = prepared;
76251
+ const absoluteOutputPath = resolveOutputPath(input2.projectDir, input2.outputPath, rendersDir, log2);
76252
+ const outputDir = dirname16(absoluteOutputPath);
76253
+ if (!existsSync42(outputDir)) mkdirSync25(outputDir, { recursive: true });
76254
+ return { input: input2, cleanupProjectDir, absoluteOutputPath };
76255
+ }
76256
+ function validateRenderOverrides(body) {
76257
+ if (body.variables !== void 0 && !isPlainObject(body.variables)) {
76258
+ return 'variables must be a JSON object keyed by variable id (e.g. {"title":"Hello"})';
76259
+ }
76260
+ return validateOutputResolutionOverride(body);
76261
+ }
76262
+ function validateOutputResolutionOverride(body) {
76263
+ if (body.outputResolution === void 0) return void 0;
76264
+ if (typeof body.outputResolution !== "string") {
76265
+ return 'outputResolution must be a string preset (e.g. "4k", "landscape-4k")';
76266
+ }
76267
+ const normalized = normalizeResolutionFlag(body.outputResolution);
76268
+ if (body.outputResolution.trim().length > 0 && normalized === void 0) {
76269
+ return `Invalid outputResolution "${body.outputResolution}". Must be one of: landscape, portrait, landscape-4k, portrait-4k, square, square-4k (aliases: 1080p, 4k, \u2026).`;
76270
+ }
76271
+ if (normalized !== void 0 && (body.format === "webm" || body.format === "mov")) {
76272
+ return `outputResolution is not supported with format "${body.format}" \u2014 the alpha (webm/mov) capture path can't supersample. Use format "mp4", or omit outputResolution to render at the composition's native dimensions.`;
76273
+ }
76274
+ return void 0;
76206
76275
  }
76207
76276
  async function prepareRenderBody(body) {
76277
+ const overrideError = validateRenderOverrides(body);
76278
+ if (overrideError) return { error: overrideError };
76208
76279
  const options = parseRenderOptions(body);
76209
76280
  const projectDir = typeof body.projectDir === "string" ? body.projectDir : void 0;
76210
76281
  if (projectDir) {
@@ -76354,15 +76425,11 @@ function createRenderHandlers(options = {}) {
76354
76425
  if ("error" in preparedResult) {
76355
76426
  return c3.json({ success: false, requestId, error: preparedResult.error }, 400);
76356
76427
  }
76357
- const { input: input2, cleanupProjectDir } = preparedResult.prepared;
76358
- const absoluteOutputPath = resolveOutputPath(
76359
- input2.projectDir,
76360
- input2.outputPath,
76428
+ const { input: input2, cleanupProjectDir, absoluteOutputPath } = resolvePreparedRenderOutput(
76429
+ preparedResult.prepared,
76361
76430
  rendersDir,
76362
76431
  log2
76363
76432
  );
76364
- const outputDir = dirname16(absoluteOutputPath);
76365
- if (!existsSync42(outputDir)) mkdirSync25(outputDir, { recursive: true });
76366
76433
  const release4 = await renderSemaphore.acquire();
76367
76434
  log2.info("render started", {
76368
76435
  requestId,
@@ -76370,16 +76437,7 @@ function createRenderHandlers(options = {}) {
76370
76437
  fps: input2.fps,
76371
76438
  quality: input2.quality
76372
76439
  });
76373
- const job = createRenderJob({
76374
- fps: input2.fps,
76375
- quality: input2.quality,
76376
- format: input2.format,
76377
- workers: input2.workers,
76378
- useGpu: input2.useGpu,
76379
- debug: input2.debug,
76380
- entryFile: input2.entryFile,
76381
- logger: log2
76382
- });
76440
+ const job = createRenderJob(buildRenderJobConfig(input2, log2));
76383
76441
  let lastLoggedPct = -10;
76384
76442
  try {
76385
76443
  await executeRenderJob(job, input2.projectDir, absoluteOutputPath, async (j3, message) => {
@@ -76465,26 +76523,13 @@ function createRenderHandlers(options = {}) {
76465
76523
  });
76466
76524
  return;
76467
76525
  }
76468
- const { input: input2, cleanupProjectDir } = preparedResult.prepared;
76469
- const absoluteOutputPath = resolveOutputPath(
76470
- input2.projectDir,
76471
- input2.outputPath,
76526
+ const { input: input2, cleanupProjectDir, absoluteOutputPath } = resolvePreparedRenderOutput(
76527
+ preparedResult.prepared,
76472
76528
  rendersDir,
76473
76529
  log2
76474
76530
  );
76475
- const outputDir = dirname16(absoluteOutputPath);
76476
- if (!existsSync42(outputDir)) mkdirSync25(outputDir, { recursive: true });
76477
76531
  log2.info("render-stream started", { requestId, projectDir: input2.projectDir });
76478
- const job = createRenderJob({
76479
- fps: input2.fps,
76480
- quality: input2.quality,
76481
- format: input2.format,
76482
- workers: input2.workers,
76483
- useGpu: input2.useGpu,
76484
- debug: input2.debug,
76485
- entryFile: input2.entryFile,
76486
- logger: log2
76487
- });
76532
+ const job = createRenderJob(buildRenderJobConfig(input2, log2));
76488
76533
  const abortController = new AbortController();
76489
76534
  const onRequestAbort = () => abortController.abort(new RenderCancelledError("request_aborted"));
76490
76535
  c3.req.raw.signal.addEventListener("abort", onRequestAbort, { once: true });
@@ -78696,12 +78741,12 @@ function createStudioServer(options) {
78696
78741
  async installRegistryBlock(opts) {
78697
78742
  const { resolveItem: resolveItem2 } = await Promise.resolve().then(() => (init_resolver(), resolver_exports));
78698
78743
  const { installItem: installItem2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
78699
- const { readFileSync: readFileSync59, writeFileSync: writeFileSync40, existsSync: existsSync81 } = await import("fs");
78744
+ const { readFileSync: readFileSync59, writeFileSync: writeFileSync40, existsSync: existsSync82 } = await import("fs");
78700
78745
  const { join: join92 } = await import("path");
78701
78746
  const item = await resolveItem2(opts.blockName);
78702
78747
  const { written } = await installItem2(item, { destDir: opts.project.dir });
78703
78748
  const indexPath = join92(opts.project.dir, "index.html");
78704
- if (existsSync81(indexPath)) {
78749
+ if (existsSync82(indexPath)) {
78705
78750
  const indexHtml = readFileSync59(indexPath, "utf-8");
78706
78751
  const hostW = indexHtml.match(/data-width="(\d+)"/)?.[1];
78707
78752
  const hostH = indexHtml.match(/data-height="(\d+)"/)?.[1];
@@ -81784,6 +81829,14 @@ async function renderDocker(projectDir, outputPath, options) {
81784
81829
  }
81785
81830
  async function renderLocal(projectDir, outputPath, options) {
81786
81831
  const producer = await loadProducer();
81832
+ if (!findFFmpeg()) {
81833
+ errorBox(
81834
+ "FFmpeg not found",
81835
+ "FFmpeg is required to encode video. The render cannot proceed without it.",
81836
+ getFFmpegInstallHint()
81837
+ );
81838
+ process.exit(1);
81839
+ }
81787
81840
  const startTime = Date.now();
81788
81841
  if (options.browserPath && !process.env.PRODUCER_HEADLESS_SHELL_PATH) {
81789
81842
  process.env.PRODUCER_HEADLESS_SHELL_PATH = options.browserPath;
@@ -81810,7 +81863,14 @@ async function renderLocal(projectDir, outputPath, options) {
81810
81863
  try {
81811
81864
  await producer.executeRenderJob(job, projectDir, outputPath, onProgress);
81812
81865
  } catch (error) {
81813
- handleRenderError(error, options, startTime, false, "Try --docker for containerized rendering");
81866
+ handleRenderError(
81867
+ error,
81868
+ options,
81869
+ startTime,
81870
+ false,
81871
+ "Try --docker for containerized rendering",
81872
+ job.failedStage
81873
+ );
81814
81874
  }
81815
81875
  const elapsed = Date.now() - startTime;
81816
81876
  trackRenderMetrics(job, elapsed, options, false);
@@ -81834,7 +81894,7 @@ function getMemorySnapshot() {
81834
81894
  memoryFreeMb: bytesToMb(freemem4())
81835
81895
  };
81836
81896
  }
81837
- function handleRenderError(error, options, startTime, docker, hint) {
81897
+ function handleRenderError(error, options, startTime, docker, hint, failedStage) {
81838
81898
  const message = normalizeErrorMessage2(error);
81839
81899
  trackRenderError({
81840
81900
  fps: fpsToNumber(options.fps),
@@ -81844,6 +81904,7 @@ function handleRenderError(error, options, startTime, docker, hint) {
81844
81904
  gpu: options.gpu,
81845
81905
  elapsedMs: Date.now() - startTime,
81846
81906
  errorMessage: message,
81907
+ failedStage,
81847
81908
  ...getMemorySnapshot()
81848
81909
  });
81849
81910
  errorBox("Render failed", message, hint);
@@ -81934,6 +81995,7 @@ var init_render2 = __esm({
81934
81995
  init_env();
81935
81996
  init_dockerRunArgs();
81936
81997
  init_errorMessage2();
81998
+ init_ffmpeg();
81937
81999
  init_src();
81938
82000
  examples7 = [
81939
82001
  ["Render to MP4", "hyperframes render --output output.mp4"],
@@ -82234,17 +82296,6 @@ var init_render2 = __esm({
82234
82296
  }
82235
82297
  console.log("");
82236
82298
  }
82237
- if (!useDocker) {
82238
- const { findFFmpeg: findFFmpeg2, getFFmpegInstallHint: getFFmpegInstallHint2 } = await Promise.resolve().then(() => (init_ffmpeg(), ffmpeg_exports));
82239
- if (!findFFmpeg2()) {
82240
- errorBox(
82241
- "FFmpeg not found",
82242
- "Rendering requires FFmpeg for video encoding.",
82243
- `Install: ${getFFmpegInstallHint2()}`
82244
- );
82245
- process.exit(1);
82246
- }
82247
- }
82248
82299
  let browserPath;
82249
82300
  if (!useDocker) {
82250
82301
  const { ensureBrowser: ensureBrowser2 } = await Promise.resolve().then(() => (init_manager2(), manager_exports2));
@@ -86542,7 +86593,7 @@ var require_extend = __commonJS({
86542
86593
  }
86543
86594
  return toStr.call(arr) === "[object Array]";
86544
86595
  };
86545
- var isPlainObject = function isPlainObject2(obj) {
86596
+ var isPlainObject2 = function isPlainObject3(obj) {
86546
86597
  if (!obj || toStr.call(obj) !== "[object Object]") {
86547
86598
  return false;
86548
86599
  }
@@ -86599,12 +86650,12 @@ var require_extend = __commonJS({
86599
86650
  src = getProperty(target, name);
86600
86651
  copy = getProperty(options, name);
86601
86652
  if (target !== copy) {
86602
- if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray3(copy)))) {
86653
+ if (deep && copy && (isPlainObject2(copy) || (copyIsArray = isArray3(copy)))) {
86603
86654
  if (copyIsArray) {
86604
86655
  copyIsArray = false;
86605
86656
  clone2 = src && isArray3(src) ? src : [];
86606
86657
  } else {
86607
- clone2 = src && isPlainObject(src) ? src : {};
86658
+ clone2 = src && isPlainObject2(src) ? src : {};
86608
86659
  }
86609
86660
  setProperty(target, { name, newValue: extend(deep, clone2, copy) });
86610
86661
  } else if (typeof copy !== "undefined") {
@@ -133389,9 +133440,12 @@ var init_statusColor = __esm({
133389
133440
  var render_exports3 = {};
133390
133441
  __export(render_exports3, {
133391
133442
  default: () => render_default2,
133392
- examples: () => examples26
133443
+ examples: () => examples26,
133444
+ resolveAspectRatioForSubmit: () => resolveAspectRatioForSubmit,
133445
+ validateResolutionFormatCombo: () => validateResolutionFormatCombo
133393
133446
  });
133394
133447
  import { isAbsolute as isAbsolute11, resolve as resolvePath4 } from "path";
133448
+ import { existsSync as existsSync80 } from "fs";
133395
133449
  function parsePollIntervalMs(raw) {
133396
133450
  const n = parseNumericFlag(raw, { flag: "--poll-interval", min: 1 });
133397
133451
  return n === void 0 ? DEFAULT_POLL_INTERVAL_MS : Math.round(n * 1e3);
@@ -133422,19 +133476,51 @@ function resolveProjectInput(opts) {
133422
133476
  if (explicit.url) return { kind: "url", url: opts.url };
133423
133477
  return { kind: "dir", dir: opts.dir ?? "." };
133424
133478
  }
133425
- function maybeAutoDetectAspectRatio(project, compositionArg, asJson) {
133479
+ function resolveAspectRatioForSubmit(project, compositionArg, explicit, asJson) {
133426
133480
  if (project.kind !== "dir") {
133427
- const reason = project.kind === "asset_id" ? "--asset-id" : "--url";
133428
- logDetection(asJson, `Auto-detect skipped (project is ${reason})`);
133429
- return void 0;
133481
+ if (!explicit) {
133482
+ const reason = project.kind === "asset_id" ? "--asset-id" : "--url";
133483
+ logDetection(asJson, `Auto-detect skipped (project is ${reason})`);
133484
+ }
133485
+ return explicit;
133430
133486
  }
133431
133487
  const dir = project.dir ?? ".";
133432
133488
  const entryRelative = compositionArg ?? "index.html";
133433
133489
  const entryPath = resolvePath4(dir, entryRelative);
133490
+ if (!existsSync80(entryPath)) {
133491
+ errorBox(
133492
+ "Composition not found",
133493
+ `Entry file "${entryRelative}" does not exist in ${dir}.`,
133494
+ "Pass --composition with a path that exists inside the project, or omit it to use index.html."
133495
+ );
133496
+ process.exit(1);
133497
+ }
133434
133498
  const detection = detectAspectRatioFromHtml(entryPath);
133499
+ if (explicit) {
133500
+ const conflictDetail = detection.kind === "matched" && detection.aspectRatio !== explicit ? `${detection.width}\xD7${detection.height} \u2192 ${detection.aspectRatio}` : detection.kind === "no-match" ? `${detection.width}\xD7${detection.height}, ratio ${detection.ratio.toFixed(2)} \u2014 not a supported ratio` : void 0;
133501
+ if (conflictDetail) {
133502
+ errorBox(
133503
+ "Aspect ratio mismatch",
133504
+ `--aspect-ratio ${explicit} doesn't match the composition (${conflictDetail}).`,
133505
+ "The renderer matches the composition's authored aspect ratio \u2014 it can't reshape it. Drop --aspect-ratio (it's auto-detected) or re-author the composition at the target ratio."
133506
+ );
133507
+ process.exit(1);
133508
+ }
133509
+ return explicit;
133510
+ }
133435
133511
  logDetection(asJson, summarizeDetection(detection, entryRelative));
133436
133512
  return detection.kind === "matched" ? detection.aspectRatio : void 0;
133437
133513
  }
133514
+ function validateResolutionFormatCombo(resolution, format) {
133515
+ if (resolution === "4k" && (format === "webm" || format === "mov")) {
133516
+ errorBox(
133517
+ "Unsupported combination",
133518
+ `--resolution 4k cannot be combined with --format ${format}.`,
133519
+ "The alpha (webm/mov) capture path doesn't support 4k supersampling. Render 4k as mp4, or render alpha at composition resolution."
133520
+ );
133521
+ process.exit(1);
133522
+ }
133523
+ }
133438
133524
  function logDetection(asJson, message) {
133439
133525
  if (asJson) return;
133440
133526
  const suffix = message.startsWith("Detected aspect ratio") ? "" : `; ${ASPECT_FALLBACK_HINT}`;
@@ -133771,7 +133857,13 @@ var init_render4 = __esm({
133771
133857
  assetId: args["asset-id"],
133772
133858
  url: args.url
133773
133859
  });
133774
- const aspectRatio = explicitAspectRatio ?? maybeAutoDetectAspectRatio(project, args.composition, asJson);
133860
+ validateResolutionFormatCombo(resolution, format);
133861
+ const aspectRatio = resolveAspectRatioForSubmit(
133862
+ project,
133863
+ args.composition,
133864
+ explicitAspectRatio,
133865
+ asJson
133866
+ );
133775
133867
  const variables = resolveVariablesAndValidateIfLocal(
133776
133868
  args.variables,
133777
133869
  args["variables-file"],
@@ -135212,15 +135304,15 @@ init_version();
135212
135304
  init_dist();
135213
135305
  import { dirname as dirname34, join as join91 } from "path";
135214
135306
  import { fileURLToPath as fileURLToPath12 } from "url";
135215
- import { existsSync as existsSync80 } from "fs";
135307
+ import { existsSync as existsSync81 } from "fs";
135216
135308
  (() => {
135217
135309
  const here = dirname34(fileURLToPath12(import.meta.url));
135218
135310
  const shader = join91(here, "shaderTransitionWorker.js");
135219
135311
  const png = join91(here, "pngDecodeBlitWorker.js");
135220
- if (!process.env.HF_SHADER_WORKER_ENTRY && existsSync80(shader)) {
135312
+ if (!process.env.HF_SHADER_WORKER_ENTRY && existsSync81(shader)) {
135221
135313
  process.env.HF_SHADER_WORKER_ENTRY = shader;
135222
135314
  }
135223
- if (!process.env.HF_PNG_DECODE_BLIT_WORKER_ENTRY && existsSync80(png)) {
135315
+ if (!process.env.HF_PNG_DECODE_BLIT_WORKER_ENTRY && existsSync81(png)) {
135224
135316
  process.env.HF_PNG_DECODE_BLIT_WORKER_ENTRY = png;
135225
135317
  }
135226
135318
  })();