@hyperframes/studio-server 0.9.7 → 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;
@@ -3933,6 +3934,7 @@ import {
3933
3934
  import {
3934
3935
  STUDIO_PREVIEW_CAPTURE_MODE,
3935
3936
  STUDIO_PREVIEW_MARK_META,
3937
+ STUDIO_PREVIEW_ERROR_KINDS,
3936
3938
  STUDIO_PREVIEW_ERRORS
3937
3939
  } from "@hyperframes/core/studio-preview-mark";
3938
3940
  import {
@@ -3951,14 +3953,14 @@ import {
3951
3953
  normalizeGsapBaseUrl,
3952
3954
  normalizeGsapLocalRoot
3953
3955
  } from "@hyperframes/core/gsap-cdn";
3954
- 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";
3955
3957
  import { isWithinProjectRoot } from "@hyperframes/parsers/asset-resolution";
3956
3958
 
3957
3959
  // src/helpers/rootImportMap.ts
3958
- import { findStartTags as findStartTags2, injectTagsAtHeadStart } from "@hyperframes/core/compiler/html-document";
3960
+ import { findStartTags as findStartTags3, injectTagsAtHeadStart } from "@hyperframes/core/compiler/html-document";
3959
3961
  var IMPORT_MAP_TYPE = /\btype\s*=\s*["']?importmap["']?(?=[\s/>])/i;
3960
3962
  function findImportMap(html) {
3961
- for (const at of findStartTags2(html, "script")) {
3963
+ for (const at of findStartTags3(html, "script")) {
3962
3964
  const open2 = /^<script\b[^>]*>/i.exec(html.slice(at));
3963
3965
  if (!open2 || !IMPORT_MAP_TYPE.test(open2[0])) continue;
3964
3966
  const start = at + open2[0].length;
@@ -4128,7 +4130,7 @@ function injectPreviewVariables(html, values) {
4128
4130
  }
4129
4131
 
4130
4132
  // src/helpers/lazyPreviewImages.ts
4131
- import { findStartTags as findStartTags3 } from "@hyperframes/core/compiler/html-document";
4133
+ import { findStartTags as findStartTags4 } from "@hyperframes/core/compiler/html-document";
4132
4134
  import { parseHTML as parseHTML6 } from "linkedom";
4133
4135
  import { STUDIO_PREVIEW_LAZY_ATTR } from "@hyperframes/core/studio-preview-mark";
4134
4136
  function startsAfterZero(el) {
@@ -4140,8 +4142,9 @@ function startsAfterZero(el) {
4140
4142
  var hasLoading = (img) => Array.from(img.attributes).some((attr) => attr.name.toLowerCase() === "loading");
4141
4143
  function lazyPreviewImages(html) {
4142
4144
  if (!/<!doctype|<html[\s>]/i.test(html)) return html;
4145
+ const tags = findStartTags4(html, "img");
4146
+ if (tags.length === 0) return html;
4143
4147
  const images = [...parseHTML6(html).document.querySelectorAll("img")];
4144
- const tags = findStartTags3(html, "img");
4145
4148
  if (tags.length !== images.length) return html;
4146
4149
  const parts = [];
4147
4150
  let from = 0;
@@ -4227,7 +4230,7 @@ function injectMotionPathPluginIfNeeded(html, baseUrl) {
4227
4230
  }
4228
4231
  function findGsapCoreScript(html) {
4229
4232
  const scripts = [...parseHTML7(html).document.querySelectorAll("script")];
4230
- const starts = findStartTags4(html, "script");
4233
+ const starts = findStartTags5(html, "script");
4231
4234
  if (starts.length !== scripts.length) return null;
4232
4235
  const at = scripts.findIndex(
4233
4236
  (script) => isGsapCoreUrl(lowerCaseAttributes(script).get("src") ?? "")
@@ -4280,7 +4283,7 @@ var GSAP_FALLBACK_ATTR = "data-hf-gsap-fallback";
4280
4283
  function gsapCdnFallbackScript(baseUrl) {
4281
4284
  return `<script ${GSAP_FALLBACK_ATTR}>
4282
4285
  (function(){
4283
- var cdnBase=${JSON.stringify(gsapCdnDist2(void 0, baseUrl)).replace(/</g, "\\u003c")};
4286
+ var cdnBase=${scriptJson(gsapCdnDist2(void 0, baseUrl))};
4284
4287
  var loaded={};
4285
4288
  function loadFallback(file){
4286
4289
  if(loaded[file])return loaded[file];
@@ -4330,15 +4333,21 @@ function previewVariablesFromRequest(rawVariables) {
4330
4333
  return { raw: rawVariables, values: parse2.values };
4331
4334
  }
4332
4335
  var PREVIEW_CAPTURE_PARAM = "hf-capture";
4333
- var EARLY_ERRORS_SCRIPT = `<script>(function(){var seen=window.${STUDIO_PREVIEW_ERRORS}=[];
4334
- addEventListener("error",function(e){seen.push(e.message||String(e))});})();</script>`;
4335
- function injectStudioPreviewAugmentations(html, adapter, projectDir, activeCompositionPath, options) {
4336
+ function scriptJson(value) {
4337
+ return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
4338
+ }
4339
+ var earlyErrorsScript = (runtimeUrl) => `<script>(function(){var seen=window.${STUDIO_PREVIEW_ERRORS}=[],kinds=window.${STUDIO_PREVIEW_ERROR_KINDS}=[],known={SyntaxError:1,TypeError:1,ReferenceError:1,RangeError:1},runtime=${scriptJson(new URL(runtimeUrl, "http://x").pathname)};
4340
+ addEventListener("error",function(e){seen.push(e.message||String(e));
4341
+ var name=e.error&&e.error.constructor&&e.error.constructor.name,path="";
4342
+ try{var url=e.filename&&new URL(e.filename,window.location.href);if(url&&url.origin===window.location.origin)path=url.pathname}catch(_){}
4343
+ kinds.push({name:known[name]===1?name:"other",origin:!path?"unknown":path===runtime?"runtime":path.indexOf("/api/projects/")===0?"project":"studio"})});})();</script>`;
4344
+ function injectStudioPreviewAugmentations(html, adapter, signature, projectDir, activeCompositionPath, options) {
4336
4345
  const marked = options.capture ? injectTagsAtHeadStart2(
4337
4346
  html,
4338
4347
  `<meta name="${STUDIO_PREVIEW_MARK_META}" content="${STUDIO_PREVIEW_CAPTURE_MODE}">`
4339
4348
  ) : injectTagsAtHeadStart2(
4340
4349
  lazyPreviewImages(html),
4341
- `<meta name="${STUDIO_PREVIEW_MARK_META}">${EARLY_ERRORS_SCRIPT}`
4350
+ `<meta name="${STUDIO_PREVIEW_MARK_META}">${earlyErrorsScript(adapter.runtimeUrl)}`
4342
4351
  );
4343
4352
  const withFallback = injectGsapCdnFallback(
4344
4353
  injectProjectSignature(
@@ -4346,7 +4355,7 @@ function injectStudioPreviewAugmentations(html, adapter, projectDir, activeCompo
4346
4355
  marked,
4347
4356
  `<meta name="${GSAP_BASE_URL_META}" content="${escapeGsapUrl(options.gsapBaseUrl)}">`
4348
4357
  ),
4349
- resolveProjectSignature(adapter, projectDir)
4358
+ signature
4350
4359
  ),
4351
4360
  options.gsapBaseUrl
4352
4361
  );
@@ -4451,6 +4460,7 @@ function registerPreviewRoutes(api, adapter) {
4451
4460
  bundled = injectStudioPreviewAugmentations(
4452
4461
  ensureHfIds2(await transformPreviewHtml(bundled, adapter, project, mainCompositionPath)),
4453
4462
  adapter,
4463
+ signature,
4454
4464
  project.dir,
4455
4465
  mainCompositionPath,
4456
4466
  { capture, gsapBaseUrl }
@@ -4464,7 +4474,7 @@ function registerPreviewRoutes(api, adapter) {
4464
4474
  mediaCodecProbeCache
4465
4475
  );
4466
4476
  bundled = addScenePartsManifest(bundled, [`meta[name="${PROJECT_SIGNATURE_META}"]`]);
4467
- if (createProjectSignature(project.dir) === signature) {
4477
+ if (await createProjectSignatureAsync(project.dir) === signature) {
4468
4478
  rememberPreview(builtKey, bundled);
4469
4479
  if (!capture) adapter.previewDocuments?.write(builtKey, bundled);
4470
4480
  }
@@ -4479,6 +4489,7 @@ function registerPreviewRoutes(api, adapter) {
4479
4489
  let fallbackAugmented = injectStudioPreviewAugmentations(
4480
4490
  await transformPreviewHtml(fallbackHtml, adapter, project, fallback.compositionPath),
4481
4491
  adapter,
4492
+ signature,
4482
4493
  project.dir,
4483
4494
  fallback.compositionPath,
4484
4495
  { capture, gsapBaseUrl }
@@ -4579,7 +4590,7 @@ ${etag}`;
4579
4590
  html = withRootImportMap(html, baseHref);
4580
4591
  recordPreviewReferences(project.dir, html);
4581
4592
  html = ensureHfIds2(await transformPreviewHtml(html, adapter, project, compPath));
4582
- html = injectStudioPreviewAugmentations(html, adapter, project.dir, compPath, {
4593
+ html = injectStudioPreviewAugmentations(html, adapter, signature, project.dir, compPath, {
4583
4594
  capture,
4584
4595
  gsapBaseUrl
4585
4596
  });
@@ -4797,6 +4808,19 @@ import { dirname as dirname5, join as join10 } from "path";
4797
4808
  import { Readable as Readable2 } from "stream";
4798
4809
  import { VALID_CANVAS_RESOLUTIONS } from "@hyperframes/parsers";
4799
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
4800
4824
  var VALID_RESOLUTIONS = new Set(VALID_CANVAS_RESOLUTIONS);
4801
4825
  var RENDER_MIME = {
4802
4826
  ".mp4": "video/mp4",
@@ -4836,12 +4860,7 @@ function registerRenderRoutes(api, adapter) {
4836
4860
  let cleanupTimer = null;
4837
4861
  const cleanupEnabled = () => typeof process !== "undefined" && process.env.NODE_ENV !== "production" && !process.argv.includes("build");
4838
4862
  const cleanupFinishedJobs = () => {
4839
- const now = Date.now();
4840
- for (const [key, job] of renderJobs) {
4841
- if (job.status !== "rendering" && now - job.createdAt > TTL_MS) {
4842
- renderJobs.delete(key);
4843
- }
4844
- }
4863
+ forgetJobsFinishedLongerThan(TTL_MS, renderJobs, (job) => job.status !== "rendering");
4845
4864
  if (renderJobs.size === 0 && cleanupTimer) {
4846
4865
  clearInterval(cleanupTimer);
4847
4866
  cleanupTimer = null;
@@ -4919,7 +4938,6 @@ function registerRenderRoutes(api, adapter) {
4919
4938
  telemetryOptOut: body.telemetryOptOut === true,
4920
4939
  ...pageMotionBlur === void 0 ? {} : { pageMotionBlur }
4921
4940
  });
4922
- jobState.createdAt = Date.now();
4923
4941
  renderJobs.set(jobId, jobState);
4924
4942
  ensureCleanupTimer();
4925
4943
  return c.json({ jobId, status: "rendering" });
@@ -5069,8 +5087,7 @@ function registerRenderRoutes(api, adapter) {
5069
5087
  id: file.id,
5070
5088
  status: file.status,
5071
5089
  progress: 100,
5072
- outputPath: file.path,
5073
- createdAt: file.createdAt
5090
+ outputPath: file.path
5074
5091
  });
5075
5092
  }
5076
5093
  }
@@ -5389,7 +5406,7 @@ function registerThumbnailRoutes(api, adapter) {
5389
5406
  const htmlFile = resolveWithinProject(project.dir, compPath);
5390
5407
  const source = htmlFile ? readFileOnce(htmlFile) : "not-a-file";
5391
5408
  if (source === "not-a-file") return c.json({ error: "not found" }, 404);
5392
- const inputSignature = compositionInputSignature(project.dir, compPath, projectSignature);
5409
+ const inputSignature = await compositionInputSignature(project.dir, compPath, projectSignature);
5393
5410
  const url = new URL(c.req.url, `http://${c.req.header("host") || "localhost"}`);
5394
5411
  const rawSeekTime = url.searchParams.get("t");
5395
5412
  const parsedSeekTime = rawSeekTime == null ? Number.NaN : parseFloat(rawSeekTime);
@@ -5472,8 +5489,8 @@ function registerThumbnailRoutes(api, adapter) {
5472
5489
  if (!generated) return null;
5473
5490
  const previewCopiesAtEnd = proxyActivityMark(project.dir);
5474
5491
  const afterGeneration = await resolveProjectAndSignature(adapter, project.id);
5475
- const inputsUnchanged = (signature) => compositionInputSignature(project.dir, compPath, signature) === inputSignature;
5476
- 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))) {
5477
5494
  return generated;
5478
5495
  }
5479
5496
  mkdirWithinProject(project.dir, cacheDir);
@@ -6412,12 +6429,11 @@ function registerMediaRoutes(api, adapter, options = {}) {
6412
6429
  const TTL_MS = 3e5;
6413
6430
  const readMediaMetadata = options.probeMediaMetadata ?? probeMediaMetadata;
6414
6431
  function cleanupFinishedJobs() {
6415
- const now = Date.now();
6416
- for (const [id, job] of mediaJobs) {
6417
- if ((job.status === "complete" || job.status === "failed") && now - job.createdAt > TTL_MS) {
6418
- mediaJobs.delete(id);
6419
- }
6420
- }
6432
+ forgetJobsFinishedLongerThan(
6433
+ TTL_MS,
6434
+ mediaJobs,
6435
+ (job) => job.status === "complete" || job.status === "failed"
6436
+ );
6421
6437
  }
6422
6438
  api.get("/projects/:id/media/metadata", async (c) => {
6423
6439
  const project = await adapter.resolveProject(c.req.param("id"));
@@ -6503,7 +6519,6 @@ function registerMediaRoutes(api, adapter, options = {}) {
6503
6519
  device: normalizeDevice(body.device),
6504
6520
  jobId
6505
6521
  });
6506
- state.createdAt = Date.now();
6507
6522
  mediaJobs.set(jobId, state);
6508
6523
  return c.json({
6509
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.7",
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/parsers": "0.9.7",
92
- "@hyperframes/core": "0.9.7"
91
+ "@hyperframes/parsers": "0.9.9",
92
+ "@hyperframes/core": "0.9.9"
93
93
  },
94
94
  "devDependencies": {
95
95
  "@types/node": "^25.0.10",