@hyperframes/studio-server 0.9.8 → 0.9.9

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.
@@ -523,13 +523,13 @@ function listProjectFiles(projectDir) {
523
523
  ctimeMs: entry.ctimeMs
524
524
  }));
525
525
  }
526
- function resolveProjectSignature(adapter, projectDir) {
527
- return adapter.getProjectSignature?.(projectDir) ?? createProjectSignature(projectDir);
526
+ async function resolveProjectSignature(adapter, projectDir) {
527
+ return await adapter.getProjectSignature?.(projectDir) ?? createProjectSignatureAsync(projectDir);
528
528
  }
529
529
  async function resolveProjectAndSignature(adapter, projectId) {
530
530
  const project = await adapter.resolveProject(projectId);
531
531
  if (!project) return null;
532
- return { project, signature: resolveProjectSignature(adapter, project.dir) };
532
+ return { project, signature: await resolveProjectSignature(adapter, project.dir) };
533
533
  }
534
534
  function planSignature(dir, collected, excluding, signedAt) {
535
535
  const files = collected.filter(
@@ -1,5 +1,5 @@
1
1
  import '@hyperframes/core';
2
- export { a as PreviewApiAdapter, P as PreviewDocumentStore, i as injectMediaCodecMap, f as injectMediaCodecMapIntoHtml, g as isAutoProxyEnabled, p as proxyEtagSalt, r as resolvePreviewMediaCodecProbeCache } from '../mediaProxyPreview-BnQDzKj1.js';
2
+ export { a as PreviewApiAdapter, P as PreviewDocumentStore, i as injectMediaCodecMap, f as injectMediaCodecMapIntoHtml, g as isAutoProxyEnabled, p as proxyEtagSalt, r as resolvePreviewMediaCodecProbeCache } from '../mediaProxyPreview-Ds5TElPq.js';
3
3
  import './mediaCodecMap.js';
4
4
  import 'sharp';
5
5
  import '@hyperframes/parsers';
@@ -24,7 +24,7 @@ import {
24
24
  recordRenderSuccess,
25
25
  registerRenderOutput,
26
26
  retainSnapshotOutput
27
- } from "../chunk-3GSVCYB4.js";
27
+ } from "../chunk-Z6G2NBCQ.js";
28
28
  import "../chunk-QF7MFJJU.js";
29
29
  import "../chunk-SVQFE44C.js";
30
30
  import "../chunk-L3HJVTQE.js";
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Hono } from 'hono';
2
- import { S as StudioApiAdapter, P as PreviewDocumentStore, M as MediaProcessingJobState } from './mediaProxyPreview-BnQDzKj1.js';
3
- export { L as LintResult, a as PreviewApiAdapter, R as RenderJobState, b as ResolvedProject, c as StudioSelectionResponse, d as StudioSelectionSnapshot, e as StudioSelectionTextField } from './mediaProxyPreview-BnQDzKj1.js';
2
+ import { S as StudioApiAdapter, P as PreviewDocumentStore, M as MediaProcessingJobState } from './mediaProxyPreview-Ds5TElPq.js';
3
+ export { L as LintResult, a as PreviewApiAdapter, R as RenderJobState, b as ResolvedProject, c as StudioSelectionResponse, d as StudioSelectionSnapshot, e as StudioSelectionTextField } from './mediaProxyPreview-Ds5TElPq.js';
4
4
  export { C as ClosedWindow, D as DEFAULT_HISTORY_ROOT, S as HISTORY_START, H as HistoryClosedError, a as HistoryEntry, b as HistoryEntrySide, c as HistoryFileChange, d as HistoryListItem, e as HistoryRefusedError, f as HistoryResult, g as HistoryTarget, h as HistoryWho, i as HistoryWindow, M as MAX_WINDOW_IDLE_MS, P as ProjectHistory, j as ProjectHistoryOptions, k as ProjectHistoryView, U as UNDO_MODES, l as UndoMode, o as openProjectHistory, r as readProjectHistory } from './projectHistory-C18HDvac.js';
5
5
  export { ArtifactCapture, ArtifactInput, ArtifactRecord, HistoryBusyError, HistoryIdError, InPlaceOutcome, ProjectHistoryRecord, PrunedHistory, ReclaimedHistory, RenderRecord, VideoState, artifactDirectory, beginArtifactCapture, finishSnapshotRun, forgetRecordedArtifacts, historyCache, isUncopiedMedia, listProjectHistories, onRestoredInPlace, pruneGoneProjectHistories, reclaimUncopiedBlobs, recordArtifact, recordArtifacts, recordRenderSuccess, registerRenderOutput, retainSnapshotOutput } from './history/index.js';
6
6
  export { CACHE_DIR_NAME as PROXY_CACHE_DIR_NAME } from './helpers/proxyTranscoder.js';
package/dist/index.js CHANGED
@@ -71,7 +71,7 @@ import {
71
71
  retainSnapshotOutput,
72
72
  serveWhileOpen,
73
73
  settledFileTag
74
- } from "./chunk-3GSVCYB4.js";
74
+ } from "./chunk-Z6G2NBCQ.js";
75
75
  import {
76
76
  clearElementScreenshotIsolation,
77
77
  getElementScreenshotClip
@@ -179,7 +179,7 @@ function registerProjectRoutes(api, adapter) {
179
179
  api.get("/projects/:id/signature", async (c) => {
180
180
  const project = await adapter.resolveProject(c.req.param("id"));
181
181
  if (!project) return c.json({ error: "not found" }, 404);
182
- return c.json({ signature: resolveProjectSignature(adapter, project.dir) });
182
+ return c.json({ signature: await resolveProjectSignature(adapter, project.dir) });
183
183
  });
184
184
  api.get("/projects/:id", async (c) => {
185
185
  const project = await adapter.resolveProject(c.req.param("id"));
@@ -280,7 +280,7 @@ import {
280
280
  RUNTIME_BOOTSTRAP_ATTR,
281
281
  stripEmbeddedRuntimeScripts
282
282
  } from "@hyperframes/core/compiler";
283
- import { isFullHtmlDocument } from "@hyperframes/core/compiler/html-document";
283
+ import { findStartTags as findStartTags2, isFullHtmlDocument } from "@hyperframes/core/compiler/html-document";
284
284
  import { gsapCdnDist } from "@hyperframes/core/gsap-cdn";
285
285
 
286
286
  // src/helpers/gsapCoreScript.ts
@@ -529,7 +529,7 @@ ${rewrittenContent}
529
529
  </html>`;
530
530
  }
531
531
  function hasBaseElement(html) {
532
- return parseHTML(html).document.querySelector("base") !== null;
532
+ return findStartTags2(html, "base").length > 0;
533
533
  }
534
534
 
535
535
  // src/helpers/compositionInsertion.ts
@@ -801,7 +801,7 @@ function rootHashes(read) {
801
801
  const source = read(ROOT_COMPOSITION) ?? "";
802
802
  return { root: sha1(source), head: sha1(rootHeadContent(source)) };
803
803
  }
804
- function compositionInputSignature(projectDir, compPath, projectSignature) {
804
+ async function compositionInputSignature(projectDir, compPath, projectSignature) {
805
805
  const key = `${projectDir}\0${compPath}`;
806
806
  const known = inputSignatures.get(key);
807
807
  if (known?.projectSignature === projectSignature) return known.inputSignature;
@@ -813,11 +813,12 @@ function compositionInputSignature(projectDir, compPath, projectSignature) {
813
813
  );
814
814
  let inputSignature;
815
815
  if (comp === ROOT_COMPOSITION) {
816
- inputSignature = createProjectSignature(projectDir, excluded);
816
+ inputSignature = await createProjectSignatureAsync(projectDir, excluded);
817
817
  } else {
818
818
  const hashes = rootHashes(read);
819
819
  if (!rootMemos.has(projectDir)) rootMemos.set(projectDir, { ...hashes, affected: null });
820
- inputSignature = `${createProjectSignature(projectDir, excluded.add(ROOT_COMPOSITION))}:${hashes.head}`;
820
+ const rest = await createProjectSignatureAsync(projectDir, excluded.add(ROOT_COMPOSITION));
821
+ inputSignature = `${rest}:${hashes.head}`;
821
822
  }
822
823
  inputSignatures.set(key, { projectSignature, inputSignature });
823
824
  return inputSignature;
@@ -3952,14 +3953,14 @@ import {
3952
3953
  normalizeGsapBaseUrl,
3953
3954
  normalizeGsapLocalRoot
3954
3955
  } from "@hyperframes/core/gsap-cdn";
3955
- import { findStartTags as findStartTags4, injectTagsAtHeadStart as injectTagsAtHeadStart2 } from "@hyperframes/core/compiler/html-document";
3956
+ import { findStartTags as findStartTags5, injectTagsAtHeadStart as injectTagsAtHeadStart2 } from "@hyperframes/core/compiler/html-document";
3956
3957
  import { isWithinProjectRoot } from "@hyperframes/parsers/asset-resolution";
3957
3958
 
3958
3959
  // src/helpers/rootImportMap.ts
3959
- import { findStartTags as findStartTags2, injectTagsAtHeadStart } from "@hyperframes/core/compiler/html-document";
3960
+ import { findStartTags as findStartTags3, injectTagsAtHeadStart } from "@hyperframes/core/compiler/html-document";
3960
3961
  var IMPORT_MAP_TYPE = /\btype\s*=\s*["']?importmap["']?(?=[\s/>])/i;
3961
3962
  function findImportMap(html) {
3962
- for (const at of findStartTags2(html, "script")) {
3963
+ for (const at of findStartTags3(html, "script")) {
3963
3964
  const open2 = /^<script\b[^>]*>/i.exec(html.slice(at));
3964
3965
  if (!open2 || !IMPORT_MAP_TYPE.test(open2[0])) continue;
3965
3966
  const start = at + open2[0].length;
@@ -4129,7 +4130,7 @@ function injectPreviewVariables(html, values) {
4129
4130
  }
4130
4131
 
4131
4132
  // src/helpers/lazyPreviewImages.ts
4132
- import { findStartTags as findStartTags3 } from "@hyperframes/core/compiler/html-document";
4133
+ import { findStartTags as findStartTags4 } from "@hyperframes/core/compiler/html-document";
4133
4134
  import { parseHTML as parseHTML6 } from "linkedom";
4134
4135
  import { STUDIO_PREVIEW_LAZY_ATTR } from "@hyperframes/core/studio-preview-mark";
4135
4136
  function startsAfterZero(el) {
@@ -4141,8 +4142,9 @@ function startsAfterZero(el) {
4141
4142
  var hasLoading = (img) => Array.from(img.attributes).some((attr) => attr.name.toLowerCase() === "loading");
4142
4143
  function lazyPreviewImages(html) {
4143
4144
  if (!/<!doctype|<html[\s>]/i.test(html)) return html;
4145
+ const tags = findStartTags4(html, "img");
4146
+ if (tags.length === 0) return html;
4144
4147
  const images = [...parseHTML6(html).document.querySelectorAll("img")];
4145
- const tags = findStartTags3(html, "img");
4146
4148
  if (tags.length !== images.length) return html;
4147
4149
  const parts = [];
4148
4150
  let from = 0;
@@ -4228,7 +4230,7 @@ function injectMotionPathPluginIfNeeded(html, baseUrl) {
4228
4230
  }
4229
4231
  function findGsapCoreScript(html) {
4230
4232
  const scripts = [...parseHTML7(html).document.querySelectorAll("script")];
4231
- const starts = findStartTags4(html, "script");
4233
+ const starts = findStartTags5(html, "script");
4232
4234
  if (starts.length !== scripts.length) return null;
4233
4235
  const at = scripts.findIndex(
4234
4236
  (script) => isGsapCoreUrl(lowerCaseAttributes(script).get("src") ?? "")
@@ -4339,7 +4341,7 @@ addEventListener("error",function(e){seen.push(e.message||String(e));
4339
4341
  var name=e.error&&e.error.constructor&&e.error.constructor.name,path="";
4340
4342
  try{var url=e.filename&&new URL(e.filename,window.location.href);if(url&&url.origin===window.location.origin)path=url.pathname}catch(_){}
4341
4343
  kinds.push({name:known[name]===1?name:"other",origin:!path?"unknown":path===runtime?"runtime":path.indexOf("/api/projects/")===0?"project":"studio"})});})();</script>`;
4342
- function injectStudioPreviewAugmentations(html, adapter, projectDir, activeCompositionPath, options) {
4344
+ function injectStudioPreviewAugmentations(html, adapter, signature, projectDir, activeCompositionPath, options) {
4343
4345
  const marked = options.capture ? injectTagsAtHeadStart2(
4344
4346
  html,
4345
4347
  `<meta name="${STUDIO_PREVIEW_MARK_META}" content="${STUDIO_PREVIEW_CAPTURE_MODE}">`
@@ -4353,7 +4355,7 @@ function injectStudioPreviewAugmentations(html, adapter, projectDir, activeCompo
4353
4355
  marked,
4354
4356
  `<meta name="${GSAP_BASE_URL_META}" content="${escapeGsapUrl(options.gsapBaseUrl)}">`
4355
4357
  ),
4356
- resolveProjectSignature(adapter, projectDir)
4358
+ signature
4357
4359
  ),
4358
4360
  options.gsapBaseUrl
4359
4361
  );
@@ -4458,6 +4460,7 @@ function registerPreviewRoutes(api, adapter) {
4458
4460
  bundled = injectStudioPreviewAugmentations(
4459
4461
  ensureHfIds2(await transformPreviewHtml(bundled, adapter, project, mainCompositionPath)),
4460
4462
  adapter,
4463
+ signature,
4461
4464
  project.dir,
4462
4465
  mainCompositionPath,
4463
4466
  { capture, gsapBaseUrl }
@@ -4471,7 +4474,7 @@ function registerPreviewRoutes(api, adapter) {
4471
4474
  mediaCodecProbeCache
4472
4475
  );
4473
4476
  bundled = addScenePartsManifest(bundled, [`meta[name="${PROJECT_SIGNATURE_META}"]`]);
4474
- if (createProjectSignature(project.dir) === signature) {
4477
+ if (await createProjectSignatureAsync(project.dir) === signature) {
4475
4478
  rememberPreview(builtKey, bundled);
4476
4479
  if (!capture) adapter.previewDocuments?.write(builtKey, bundled);
4477
4480
  }
@@ -4486,6 +4489,7 @@ function registerPreviewRoutes(api, adapter) {
4486
4489
  let fallbackAugmented = injectStudioPreviewAugmentations(
4487
4490
  await transformPreviewHtml(fallbackHtml, adapter, project, fallback.compositionPath),
4488
4491
  adapter,
4492
+ signature,
4489
4493
  project.dir,
4490
4494
  fallback.compositionPath,
4491
4495
  { capture, gsapBaseUrl }
@@ -4586,7 +4590,7 @@ ${etag}`;
4586
4590
  html = withRootImportMap(html, baseHref);
4587
4591
  recordPreviewReferences(project.dir, html);
4588
4592
  html = ensureHfIds2(await transformPreviewHtml(html, adapter, project, compPath));
4589
- html = injectStudioPreviewAugmentations(html, adapter, project.dir, compPath, {
4593
+ html = injectStudioPreviewAugmentations(html, adapter, signature, project.dir, compPath, {
4590
4594
  capture,
4591
4595
  gsapBaseUrl
4592
4596
  });
@@ -4804,6 +4808,19 @@ import { dirname as dirname5, join as join10 } from "path";
4804
4808
  import { Readable as Readable2 } from "stream";
4805
4809
  import { VALID_CANVAS_RESOLUTIONS } from "@hyperframes/parsers";
4806
4810
  import { formatRenderOutputTimestamp, parseFps, fpsToNumber } from "@hyperframes/core";
4811
+
4812
+ // src/helpers/finishedJobs.ts
4813
+ var firstSeenFinishedAt = /* @__PURE__ */ new WeakMap();
4814
+ function forgetJobsFinishedLongerThan(ttlMs, jobs, isFinished, now = Date.now()) {
4815
+ for (const [id, job] of jobs) {
4816
+ if (!isFinished(job)) continue;
4817
+ const finishedAt = firstSeenFinishedAt.get(job) ?? now;
4818
+ firstSeenFinishedAt.set(job, finishedAt);
4819
+ if (now - finishedAt > ttlMs) jobs.delete(id);
4820
+ }
4821
+ }
4822
+
4823
+ // src/routes/render.ts
4807
4824
  var VALID_RESOLUTIONS = new Set(VALID_CANVAS_RESOLUTIONS);
4808
4825
  var RENDER_MIME = {
4809
4826
  ".mp4": "video/mp4",
@@ -4843,12 +4860,7 @@ function registerRenderRoutes(api, adapter) {
4843
4860
  let cleanupTimer = null;
4844
4861
  const cleanupEnabled = () => typeof process !== "undefined" && process.env.NODE_ENV !== "production" && !process.argv.includes("build");
4845
4862
  const cleanupFinishedJobs = () => {
4846
- const now = Date.now();
4847
- for (const [key, job] of renderJobs) {
4848
- if (job.status !== "rendering" && now - job.createdAt > TTL_MS) {
4849
- renderJobs.delete(key);
4850
- }
4851
- }
4863
+ forgetJobsFinishedLongerThan(TTL_MS, renderJobs, (job) => job.status !== "rendering");
4852
4864
  if (renderJobs.size === 0 && cleanupTimer) {
4853
4865
  clearInterval(cleanupTimer);
4854
4866
  cleanupTimer = null;
@@ -4926,7 +4938,6 @@ function registerRenderRoutes(api, adapter) {
4926
4938
  telemetryOptOut: body.telemetryOptOut === true,
4927
4939
  ...pageMotionBlur === void 0 ? {} : { pageMotionBlur }
4928
4940
  });
4929
- jobState.createdAt = Date.now();
4930
4941
  renderJobs.set(jobId, jobState);
4931
4942
  ensureCleanupTimer();
4932
4943
  return c.json({ jobId, status: "rendering" });
@@ -5076,8 +5087,7 @@ function registerRenderRoutes(api, adapter) {
5076
5087
  id: file.id,
5077
5088
  status: file.status,
5078
5089
  progress: 100,
5079
- outputPath: file.path,
5080
- createdAt: file.createdAt
5090
+ outputPath: file.path
5081
5091
  });
5082
5092
  }
5083
5093
  }
@@ -5396,7 +5406,7 @@ function registerThumbnailRoutes(api, adapter) {
5396
5406
  const htmlFile = resolveWithinProject(project.dir, compPath);
5397
5407
  const source = htmlFile ? readFileOnce(htmlFile) : "not-a-file";
5398
5408
  if (source === "not-a-file") return c.json({ error: "not found" }, 404);
5399
- const inputSignature = compositionInputSignature(project.dir, compPath, projectSignature);
5409
+ const inputSignature = await compositionInputSignature(project.dir, compPath, projectSignature);
5400
5410
  const url = new URL(c.req.url, `http://${c.req.header("host") || "localhost"}`);
5401
5411
  const rawSeekTime = url.searchParams.get("t");
5402
5412
  const parsedSeekTime = rawSeekTime == null ? Number.NaN : parseFloat(rawSeekTime);
@@ -5479,8 +5489,8 @@ function registerThumbnailRoutes(api, adapter) {
5479
5489
  if (!generated) return null;
5480
5490
  const previewCopiesAtEnd = proxyActivityMark(project.dir);
5481
5491
  const afterGeneration = await resolveProjectAndSignature(adapter, project.id);
5482
- const inputsUnchanged = (signature) => compositionInputSignature(project.dir, compPath, signature) === inputSignature;
5483
- if (previewCopiesAtStart === null || previewCopiesAtEnd !== previewCopiesAtStart || afterGeneration?.project.dir !== project.dir || !inputsUnchanged(afterGeneration.signature) || !inputsUnchanged(createProjectSignature(project.dir))) {
5492
+ const inputsUnchanged = async (signature) => await compositionInputSignature(project.dir, compPath, signature) === inputSignature;
5493
+ if (previewCopiesAtStart === null || previewCopiesAtEnd !== previewCopiesAtStart || afterGeneration?.project.dir !== project.dir || !await inputsUnchanged(afterGeneration.signature) || !await inputsUnchanged(await createProjectSignatureAsync(project.dir))) {
5484
5494
  return generated;
5485
5495
  }
5486
5496
  mkdirWithinProject(project.dir, cacheDir);
@@ -6419,12 +6429,11 @@ function registerMediaRoutes(api, adapter, options = {}) {
6419
6429
  const TTL_MS = 3e5;
6420
6430
  const readMediaMetadata = options.probeMediaMetadata ?? probeMediaMetadata;
6421
6431
  function cleanupFinishedJobs() {
6422
- const now = Date.now();
6423
- for (const [id, job] of mediaJobs) {
6424
- if ((job.status === "complete" || job.status === "failed") && now - job.createdAt > TTL_MS) {
6425
- mediaJobs.delete(id);
6426
- }
6427
- }
6432
+ forgetJobsFinishedLongerThan(
6433
+ TTL_MS,
6434
+ mediaJobs,
6435
+ (job) => job.status === "complete" || job.status === "failed"
6436
+ );
6428
6437
  }
6429
6438
  api.get("/projects/:id/media/metadata", async (c) => {
6430
6439
  const project = await adapter.resolveProject(c.req.param("id"));
@@ -6510,7 +6519,6 @@ function registerMediaRoutes(api, adapter, options = {}) {
6510
6519
  device: normalizeDevice(body.device),
6511
6520
  jobId
6512
6521
  });
6513
- state.createdAt = Date.now();
6514
6522
  mediaJobs.set(jobId, state);
6515
6523
  return c.json({
6516
6524
  jobId,
@@ -118,7 +118,7 @@ interface StudioApiAdapter {
118
118
  /** Bundle a project directory into a single HTML string, forwarding `options` over the host's own. */
119
119
  bundle(projectDir: string, options?: Pick<BundleOptions, "stampHfIds" | "onRead">): Promise<string | null>;
120
120
  /** Optional: a cached `createProjectSignature(dir)`; preview caching checks builds against it. */
121
- getProjectSignature?: (projectDir: string) => string;
121
+ getProjectSignature?: (projectDir: string) => string | Promise<string>;
122
122
  invalidateProjectSignature?: (projectDir: string) => void;
123
123
  loadSharp?: () => Promise<typeof sharp.default>;
124
124
  /** Lint a single HTML string. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio-server",
3
- "version": "0.9.8",
3
+ "version": "0.9.9",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -88,8 +88,8 @@
88
88
  "linkedom": "^0.18.12",
89
89
  "postcss": "^8.5.8",
90
90
  "postcss-selector-parser": "^7.1.2",
91
- "@hyperframes/core": "0.9.8",
92
- "@hyperframes/parsers": "0.9.8"
91
+ "@hyperframes/parsers": "0.9.9",
92
+ "@hyperframes/core": "0.9.9"
93
93
  },
94
94
  "devDependencies": {
95
95
  "@types/node": "^25.0.10",