@hyperframes/studio-server 0.8.193 → 0.8.195

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.
@@ -512,14 +512,41 @@ async function cloneOrCopy(from, to, beforeReplace) {
512
512
  throw error;
513
513
  }
514
514
  }
515
+ var listDir = (path) => readdir(path, { withFileTypes: true }).catch((error) => {
516
+ if (error.code === "ENOENT") return [];
517
+ throw error;
518
+ });
515
519
  async function openBlobStore(dir) {
516
520
  const sizes = /* @__PURE__ */ new Map();
521
+ const asideDir = join2(dirname(dir), "aside");
522
+ const asideSizes = /* @__PURE__ */ new Map();
523
+ const holds = /* @__PURE__ */ new Map();
517
524
  await mkdir(dir, { recursive: true });
518
- for (const shard of await readdir(dir))
519
- for (const hash of await readdir(join2(dir, shard)).catch(() => []))
520
- sizes.set(hash, (await stat(join2(dir, shard, hash))).size);
521
- const pathOf = (hash) => blobPath(dir, hash);
525
+ for (const [root, found] of [
526
+ [dir, sizes],
527
+ [asideDir, asideSizes]
528
+ ])
529
+ for (const shard of await listDir(root)) {
530
+ if (!shard.isDirectory()) {
531
+ if (shard.name.startsWith("incoming-")) await rm(join2(root, shard.name), { force: true });
532
+ continue;
533
+ }
534
+ for (const hash of await listDir(join2(root, shard.name)))
535
+ found.set(hash.name, (await stat(join2(root, shard.name, hash.name))).size);
536
+ }
537
+ for (const hash of asideSizes.keys()) holds.set(hash, /* @__PURE__ */ new Set(["*"]));
538
+ const pathOf = (hash) => blobPath(sizes.has(hash) ? dir : asideDir, hash);
522
539
  let total = [...sizes.values()].reduce((sum, size) => sum + size, 0);
540
+ const forget = async (hash) => {
541
+ await rm(blobPath(dir, hash), { force: true });
542
+ total -= sizes.get(hash) ?? 0;
543
+ sizes.delete(hash);
544
+ };
545
+ const forgetAside = async (hash) => {
546
+ await rm(blobPath(asideDir, hash), { force: true });
547
+ asideSizes.delete(hash);
548
+ holds.delete(hash);
549
+ };
523
550
  return {
524
551
  async put(absPath) {
525
552
  const temp = join2(dir, `incoming-${randomUUID3()}`);
@@ -533,35 +560,55 @@ async function openBlobStore(dir) {
533
560
  await rm(temp, { force: true });
534
561
  return hash;
535
562
  }
536
- await mkdir(dirname(pathOf(hash)), { recursive: true });
563
+ await mkdir(dirname(blobPath(dir, hash)), { recursive: true });
537
564
  const size = (await stat(temp)).size;
538
- await rename(temp, pathOf(hash));
565
+ await rename(temp, blobPath(dir, hash));
539
566
  sizes.set(hash, size);
540
567
  total += size;
541
568
  return hash;
542
569
  },
543
570
  reference: (absPath) => hashFile(absPath),
544
- has: (hash) => sizes.has(hash),
545
- read: async (hash) => readBlob(dir, hash),
571
+ has: (hash) => sizes.has(hash) || asideSizes.has(hash),
572
+ read: (hash) => readFile(pathOf(hash)),
546
573
  writeTo: async (hash, absPath, beforeReplace) => cloneOrCopy(pathOf(hash), absPath, beforeReplace),
574
+ async release(held) {
575
+ for (const [hash, path] of held) {
576
+ holds.get(hash)?.delete(path);
577
+ if (holds.get(hash)?.size === 0) await forgetAside(hash);
578
+ }
579
+ },
580
+ async keep(path, absPath, remove) {
581
+ const hash = await hashFile(absPath);
582
+ if (sizes.has(hash)) return;
583
+ if (asideSizes.has(hash)) {
584
+ holds.get(hash).add(path);
585
+ return;
586
+ }
587
+ const to = blobPath(asideDir, hash);
588
+ await mkdir(dirname(to), { recursive: true });
589
+ const moved = remove && await rename(absPath, to).then(
590
+ () => true,
591
+ () => false
592
+ );
593
+ if (!moved) {
594
+ const temp = join2(asideDir, `incoming-${randomUUID3()}`);
595
+ await cloneOrStream(absPath, temp).catch(async (error) => {
596
+ await rm(temp, { force: true });
597
+ throw error;
598
+ });
599
+ await rename(temp, to);
600
+ }
601
+ asideSizes.set(hash, (await stat(to)).size);
602
+ holds.set(hash, /* @__PURE__ */ new Set([path]));
603
+ },
547
604
  bytes: () => total,
548
605
  size: (hash) => sizes.get(hash) ?? 0,
549
606
  async drop(hashes) {
550
- for (const hash of hashes) {
551
- const size = sizes.get(hash);
552
- if (size === void 0) continue;
553
- await rm(pathOf(hash), { force: true });
554
- sizes.delete(hash);
555
- total -= size;
556
- }
607
+ for (const hash of hashes) if (sizes.has(hash)) await forget(hash);
557
608
  },
558
609
  async prune(keep) {
559
- for (const [hash, size] of sizes) {
560
- if (keep.has(hash)) continue;
561
- await rm(pathOf(hash), { force: true });
562
- sizes.delete(hash);
563
- total -= size;
564
- }
610
+ for (const hash of [...sizes.keys()]) if (!keep.has(hash)) await forget(hash);
611
+ for (const hash of [...asideSizes.keys()]) if (!keep.has(hash)) await forgetAside(hash);
565
612
  }
566
613
  };
567
614
  }
@@ -2309,6 +2356,9 @@ var Engine = class {
2309
2356
  this.windows.push(group);
2310
2357
  try {
2311
2358
  for (const [path, hash] of changes) await this.writeProjectFile(path, hash);
2359
+ await this.blobs.release(
2360
+ changes.flatMap(([path, hash]) => hash ? [[hash, path]] : [])
2361
+ );
2312
2362
  await this.sweep();
2313
2363
  } finally {
2314
2364
  this.windows = this.windows.filter((open) => open !== group);
@@ -2324,6 +2374,9 @@ var Engine = class {
2324
2374
  const version = hash === null ? DELETED_VERSION : hashVersion(hash);
2325
2375
  recordFileWriteReceipt(absPath, { path, version, writeToken });
2326
2376
  }
2377
+ const standing = lstatSync4(absPath, { throwIfNoEntry: false });
2378
+ if (standing?.isFile() && isUncopied(path, standing.size))
2379
+ await this.blobs.keep(path, absPath, hash === null);
2327
2380
  if (hash === null) {
2328
2381
  this.assertWritable();
2329
2382
  rmSync3(absPath, { force: true });
@@ -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-C0DZX2Mo.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-CdpmyC5i.js';
3
3
  import './mediaCodecMap.js';
4
4
  import 'sharp';
5
5
  import '@hyperframes/parsers';
@@ -22,7 +22,7 @@ import {
22
22
  recordRenderSuccess,
23
23
  registerRenderOutput,
24
24
  retainSnapshotOutput
25
- } from "../chunk-JPKAFJIJ.js";
25
+ } from "../chunk-4HP26XV4.js";
26
26
  import "../chunk-QF7MFJJU.js";
27
27
  import "../chunk-SVQFE44C.js";
28
28
  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-C0DZX2Mo.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-C0DZX2Mo.js';
2
+ import { S as StudioApiAdapter, P as PreviewDocumentStore, M as MediaProcessingJobState } from './mediaProxyPreview-CdpmyC5i.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-CdpmyC5i.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, ProjectHistoryRecord, PrunedHistory, ReclaimedHistory, RenderRecord, VideoState, artifactDirectory, beginArtifactCapture, finishSnapshotRun, forgetRecordedArtifacts, historyCache, listProjectHistories, 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';
@@ -188,7 +188,7 @@ declare const consumeFileWriteReceipt: typeof identifyFileWrite;
188
188
  * by last-write-wins script execution order — this is intentional so the
189
189
  * composition can pin a specific version.
190
190
  */
191
- declare function buildSubCompositionHtml(projectDir: string, compPath: string, runtimeUrl: string, baseHref?: string, rawOverride?: string): string | null;
191
+ declare function buildSubCompositionHtml(projectDir: string, compPath: string, runtimeUrl: string, baseHref?: string, rawOverride?: string, gsapBaseUrl?: string): string | null;
192
192
 
193
193
  interface ThumbnailOutputDimensions {
194
194
  width: number;
package/dist/index.js CHANGED
@@ -62,7 +62,7 @@ import {
62
62
  resolveProjectSignature,
63
63
  retainSnapshotOutput,
64
64
  settledFileTag
65
- } from "./chunk-JPKAFJIJ.js";
65
+ } from "./chunk-4HP26XV4.js";
66
66
  import {
67
67
  clearElementScreenshotIsolation,
68
68
  getElementScreenshotClip
@@ -267,6 +267,53 @@ import {
267
267
  } from "@hyperframes/core/compiler";
268
268
  import { isFullHtmlDocument } from "@hyperframes/core/compiler/html-document";
269
269
  import { gsapCdnDist } from "@hyperframes/core/gsap-cdn";
270
+
271
+ // src/helpers/gsapCoreScript.ts
272
+ import { findStartTags, insertBeforeCloseTag } from "@hyperframes/core/compiler/html-document";
273
+ var URL_BASE = "http://localhost/";
274
+ function urlPath(src) {
275
+ return URL.canParse(src, URL_BASE) ? new URL(src, URL_BASE).pathname : null;
276
+ }
277
+ function isGsapCoreUrl(src) {
278
+ return /\/gsap(-\d+(\.\d+)*)?(\.min)?\.js$/i.test(urlPath(src) ?? "");
279
+ }
280
+ var GSAP_PLUGIN_FILE = /\/(\w+Plugin|CustomBounce|CustomEase|CustomWiggle|Draggable|EasePack|Flip|GSDevTools|MotionPathHelper|Observer|ScrollSmoother|ScrollTrigger|SplitText)(\.min)?\.js$/i;
281
+ function isGsapPluginUrl(src) {
282
+ const path = urlPath(src);
283
+ if (!path || isGsapCoreUrl(src)) return false;
284
+ return GSAP_PLUGIN_FILE.test(path) || /\/gsap(@[^/]*)?\/(.+\/)?[^/]+\.js$/i.test(path);
285
+ }
286
+ var TEMPLATE_ELEMENT = /<template\b[^>]*>[\s\S]*?<\/template>/gi;
287
+ var SCRIPT_SRC = /<script\b[^>]*?\ssrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
288
+ var INLINED_MARKER = /\/\*\s*inlined:\s*(\S+)\s*\*\//gi;
289
+ var CORE_LICENSE_BANNER = /\/\*!\s*\*\s*GSAP\s+\d/;
290
+ var PLUGIN_LICENSE_BANNER = /\/\*!\s*\*\s*(?!GSAP\s)\w+\s+\d[^*]*\*\s*https:\/\/gsap\.com/;
291
+ var CORE_BODY = /Missing plugin\? gsap\.registerPlugin\(\)/;
292
+ function scriptUrls(html) {
293
+ const srcs = [...html.matchAll(SCRIPT_SRC)].map(
294
+ (m) => (m[1] ?? m[2] ?? m[3] ?? "").replace(/&amp;/gi, "&")
295
+ );
296
+ return [...srcs, ...[...html.matchAll(INLINED_MARKER)].map((m) => m[1] ?? "")];
297
+ }
298
+ function htmlHasGsapCore(html) {
299
+ const live = html.replace(TEMPLATE_ELEMENT, "");
300
+ return scriptUrls(live).some(isGsapCoreUrl) || CORE_LICENSE_BANNER.test(live) || CORE_BODY.test(live);
301
+ }
302
+ function isGsapPluginScript(script) {
303
+ return scriptUrls(script).some(isGsapPluginUrl) || PLUGIN_LICENSE_BANNER.test(script);
304
+ }
305
+ function insertGsapCoreBeforeHeadPlugin(html, coreTag) {
306
+ const lowered = html.toLowerCase();
307
+ const at = findStartTags(html, "script").find((start) => {
308
+ const close = lowered.indexOf("</script", start);
309
+ return isGsapPluginScript(html.slice(start, close === -1 ? void 0 : close));
310
+ });
311
+ if (at === void 0 || insertBeforeCloseTag(html.slice(0, at), "head", "") !== null) return null;
312
+ return `${html.slice(0, at)}${coreTag}
313
+ ${html.slice(at)}`;
314
+ }
315
+
316
+ // src/helpers/subComposition.ts
270
317
  function rewriteRelativePaths(root, compPath, projectDir) {
271
318
  const assetExists = (path) => existsSync(join2(projectDir, path));
272
319
  rewriteAssetPaths(
@@ -367,7 +414,7 @@ function tagRootCompositionFile(bodyHtml, compPath) {
367
414
  function rootHeadContent(rootHtml) {
368
415
  return rootHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i)?.[1] ?? "";
369
416
  }
370
- function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, rawOverride) {
417
+ function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, rawOverride, gsapBaseUrl) {
371
418
  const compFile = join2(projectDir, compPath);
372
419
  if (!existsSync(compFile)) return null;
373
420
  const rawComp = rawOverride ?? readFileSync(compFile, "utf-8");
@@ -417,9 +464,10 @@ ${compHeadContent}`;
417
464
  headContent = stripEmbeddedRuntimeScripts(headContent);
418
465
  headContent += `
419
466
  <script ${RUNTIME_BOOTSTRAP_ATTR}="1" src="${runtimeUrl}"></script>`;
420
- if (!headContent.includes("gsap")) {
421
- headContent += `
422
- <script src="${gsapCdnDist()}gsap.min.js"></script>`;
467
+ if (!htmlHasGsapCore(headContent)) {
468
+ const core = `<script src="${gsapCdnDist(void 0, gsapBaseUrl).replace(/&/g, "&amp;")}gsap.min.js"></script>`;
469
+ headContent = insertGsapCoreBeforeHeadPlugin(headContent, core) ?? `${headContent}
470
+ ${core}`;
423
471
  }
424
472
  const htmlOpen = htmlAttrs ? `<html ${htmlAttrs}>` : "<html>";
425
473
  const bodyOpen = bodyAttrs ? `<body ${bodyAttrs}>` : "<body>";
@@ -1225,7 +1273,7 @@ function resolveGsapWriter(env) {
1225
1273
  // src/routes/files.ts
1226
1274
  import {
1227
1275
  hasCompositionOutsideTemplates,
1228
- insertBeforeCloseTag
1276
+ insertBeforeCloseTag as insertBeforeCloseTag2
1229
1277
  } from "@hyperframes/core/compiler/html-document";
1230
1278
  async function loadGsapParser() {
1231
1279
  return import("@hyperframes/parsers/gsap-parser-recast");
@@ -2097,8 +2145,8 @@ async function prepareGsapMutationScript(c, res, firstMutation) {
2097
2145
  "</script>"
2098
2146
  ].join("\n");
2099
2147
  const [first, second] = hasCompositionOutsideTemplates(html) ? ["body", "template"] : ["template", "body"];
2100
- html = insertBeforeCloseTag(html, first, `${bootstrap}
2101
- `) ?? insertBeforeCloseTag(html, second, `${bootstrap}
2148
+ html = insertBeforeCloseTag2(html, first, `${bootstrap}
2149
+ `) ?? insertBeforeCloseTag2(html, second, `${bootstrap}
2102
2150
  `) ?? `${html}
2103
2151
  ${bootstrap}`;
2104
2152
  block = extractGsapScriptBlock(html);
@@ -3828,7 +3876,7 @@ import {
3828
3876
  AFTER_FONTS_SCRIPT_TYPE,
3829
3877
  addScenePartsManifest,
3830
3878
  RUNTIME_BOOTSTRAP_ATTR as RUNTIME_BOOTSTRAP_ATTR2,
3831
- insertBeforeCloseTag as insertBeforeCloseTag2,
3879
+ insertBeforeCloseTag as insertBeforeCloseTag3,
3832
3880
  insertRuntimeTag,
3833
3881
  stripEmbeddedRuntimeScripts as stripEmbeddedRuntimeScripts2
3834
3882
  } from "@hyperframes/core/compiler";
@@ -3838,8 +3886,14 @@ import {
3838
3886
  STUDIO_PREVIEW_ERRORS
3839
3887
  } from "@hyperframes/core/studio-preview-mark";
3840
3888
  import { PREVIEW_PROXY_BOX_PARAM, parsePreviewProxyBox } from "@hyperframes/core";
3841
- import { gsapCdnDist as gsapCdnDist2, motionPathPluginUrl } from "@hyperframes/core/gsap-cdn";
3842
- import { findStartTags as findStartTags2, injectTagsAtHeadStart } from "@hyperframes/core/compiler/html-document";
3889
+ import {
3890
+ GSAP_BASE_URL_META,
3891
+ gsapCdnDist as gsapCdnDist2,
3892
+ motionPathPluginUrl,
3893
+ normalizeGsapBaseUrl,
3894
+ normalizeGsapLocalRoot
3895
+ } from "@hyperframes/core/gsap-cdn";
3896
+ import { findStartTags as findStartTags3, injectTagsAtHeadStart } from "@hyperframes/core/compiler/html-document";
3843
3897
  import { isWithinProjectRoot } from "@hyperframes/parsers/asset-resolution";
3844
3898
  import { ensureHfIds as ensureHfIds2 } from "@hyperframes/parsers/hf-ids";
3845
3899
 
@@ -3974,7 +4028,7 @@ function injectPreviewVariables(html, values) {
3974
4028
  }
3975
4029
 
3976
4030
  // src/helpers/lazyPreviewImages.ts
3977
- import { findStartTags } from "@hyperframes/core/compiler/html-document";
4031
+ import { findStartTags as findStartTags2 } from "@hyperframes/core/compiler/html-document";
3978
4032
  import { parseHTML as parseHTML6 } from "linkedom";
3979
4033
  import { STUDIO_PREVIEW_LAZY_ATTR } from "@hyperframes/core/studio-preview-mark";
3980
4034
  function startsAfterZero(el) {
@@ -3987,7 +4041,7 @@ var hasLoading = (img) => Array.from(img.attributes).some((attr) => attr.name.to
3987
4041
  function lazyPreviewImages(html) {
3988
4042
  if (!/<!doctype|<html[\s>]/i.test(html)) return html;
3989
4043
  const images = [...parseHTML6(html).document.querySelectorAll("img")];
3990
- const tags = findStartTags(html, "img");
4044
+ const tags = findStartTags2(html, "img");
3991
4045
  if (tags.length !== images.length) return html;
3992
4046
  const parts = [];
3993
4047
  let from = 0;
@@ -4003,9 +4057,12 @@ function lazyPreviewImages(html) {
4003
4057
 
4004
4058
  // src/routes/preview.ts
4005
4059
  var PROJECT_SIGNATURE_META = "hyperframes-project-signature";
4006
- var GSAP_CDN_SCRIPT = `<script src="${gsapCdnDist2()}gsap.min.js"></script>`;
4007
- var GSAP_CUSTOM_EASE_CDN_SCRIPT = `<script src="${gsapCdnDist2()}CustomEase.min.js"></script>`;
4008
- var GSAP_MOTION_PATH_CDN_SCRIPT = `<script src="${motionPathPluginUrl()}"></script>`;
4060
+ function escapeGsapUrl(url) {
4061
+ return url.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
4062
+ }
4063
+ function gsapScriptTag(file, baseUrl) {
4064
+ return `<script src="${escapeGsapUrl(gsapCdnDist2(void 0, baseUrl) + file)}"></script>`;
4065
+ }
4009
4066
  function injectProjectSignature(html, signature) {
4010
4067
  const tag = `<meta name="${PROJECT_SIGNATURE_META}" content="${signature}">`;
4011
4068
  if (html.includes(`name="${PROJECT_SIGNATURE_META}"`)) {
@@ -4014,7 +4071,7 @@ function injectProjectSignature(html, signature) {
4014
4071
  tag
4015
4072
  );
4016
4073
  }
4017
- return insertBeforeCloseTag2(html, "head", `${tag}
4074
+ return insertBeforeCloseTag3(html, "head", `${tag}
4018
4075
  `) ?? `${tag}
4019
4076
  ${html}`;
4020
4077
  }
@@ -4044,14 +4101,10 @@ function withPreviewRuntime(html, runtimeUrl) {
4044
4101
  return insertRuntimeTag(stripEmbeddedRuntimeScripts2(html), tag);
4045
4102
  }
4046
4103
  function injectScriptTagIntoHead(html, scriptTag) {
4047
- return insertBeforeCloseTag2(html, "head", `${scriptTag}
4104
+ return insertBeforeCloseTag3(html, "head", `${scriptTag}
4048
4105
  `) ?? `${scriptTag}
4049
4106
  ${html}`;
4050
4107
  }
4051
- function htmlHasGsap(html) {
4052
- const outsideTemplates = html.replace(/<template\b[^>]*>[\s\S]*?<\/template>/gi, "");
4053
- return /<script\b[^>]*src=["'][^"']*gsap/i.test(outsideTemplates) || /\/\*\s*inlined:.*gsap/i.test(outsideTemplates) || /\b(GreenSock|_gsScope)\b/.test(outsideTemplates) || /\bgsap\.(config|defaults|registerPlugin|version)\b/.test(outsideTemplates);
4054
- }
4055
4108
  function htmlHasCustomEase(html) {
4056
4109
  return /<script\b[^>]*src=["'][^"']*CustomEase/i.test(html) || /\bwindow\.CustomEase\b/.test(html) || /\bCustomEase\s*=\s*/.test(html);
4057
4110
  }
@@ -4061,24 +4114,20 @@ function htmlUsesMotionPath(html) {
4061
4114
  function htmlHasMotionPathPlugin(html) {
4062
4115
  return /<script\b[^>]*src=["'][^"']*MotionPathPlugin/i.test(html) || /\bwindow\.MotionPathPlugin\b/.test(html) || /\bMotionPathPlugin\s*=\s*/.test(html);
4063
4116
  }
4064
- function injectMotionPathPluginIfNeeded(html) {
4117
+ function injectMotionPathPluginIfNeeded(html, baseUrl) {
4065
4118
  if (!htmlUsesMotionPath(html) || htmlHasMotionPathPlugin(html)) return html;
4066
4119
  const gsap = findGsapCoreScript(html);
4067
4120
  if (gsap) {
4068
4121
  const version = gsap.src.match(/gsap@([\d.]+)/)?.[1];
4069
4122
  const ordering = `${gsap.type ? ` type="${gsap.type}"` : ""}${gsap.defer ? " defer" : ""}`;
4070
- const pluginTag = `<script${ordering} src="${motionPathPluginUrl(version)}"></script>`;
4123
+ const pluginTag = `<script${ordering} src="${escapeGsapUrl(motionPathPluginUrl(version, baseUrl))}"></script>`;
4071
4124
  return html.slice(0, gsap.end) + "\n" + pluginTag + html.slice(gsap.end);
4072
4125
  }
4073
- return injectScriptTagIntoHead(html, GSAP_MOTION_PATH_CDN_SCRIPT);
4074
- }
4075
- function isGsapCoreUrl(src) {
4076
- const base = "http://localhost/";
4077
- return URL.canParse(src, base) && /\/gsap(\.min)?\.js$/i.test(new URL(src, base).pathname);
4126
+ return injectScriptTagIntoHead(html, gsapScriptTag("MotionPathPlugin.min.js", baseUrl));
4078
4127
  }
4079
4128
  function findGsapCoreScript(html) {
4080
4129
  const scripts = [...parseHTML7(html).document.querySelectorAll("script")];
4081
- const starts = findStartTags2(html, "script");
4130
+ const starts = findStartTags3(html, "script");
4082
4131
  if (starts.length !== scripts.length) return null;
4083
4132
  const at = scripts.findIndex(
4084
4133
  (script) => isGsapCoreUrl(lowerCaseAttributes(script).get("src") ?? "")
@@ -4103,30 +4152,35 @@ function scriptCloseEnd(html, start) {
4103
4152
  const closeAt = close.exec(html)?.index;
4104
4153
  return closeAt === void 0 ? 0 : html.indexOf(">", closeAt) + 1;
4105
4154
  }
4106
- function injectStudioMotionDependencies(html, manifestContent) {
4155
+ function injectStudioMotionDependencies(html, manifestContent, baseUrl) {
4107
4156
  const manifest = parseStudioMotionManifestContent(manifestContent);
4108
4157
  if (!manifest.hasMotion) return html;
4109
4158
  let next = html;
4110
- if (!htmlHasGsap(next)) next = injectScriptTagIntoHead(next, GSAP_CDN_SCRIPT);
4159
+ if (!htmlHasGsapCore(next)) {
4160
+ const core = gsapScriptTag("gsap.min.js", baseUrl);
4161
+ next = insertGsapCoreBeforeHeadPlugin(next, core) ?? injectScriptTagIntoHead(next, core);
4162
+ }
4111
4163
  if (manifest.hasCustomEase && !htmlHasCustomEase(next)) {
4112
- next = injectScriptTagIntoHead(next, GSAP_CUSTOM_EASE_CDN_SCRIPT);
4164
+ next = injectScriptTagIntoHead(next, gsapScriptTag("CustomEase.min.js", baseUrl));
4113
4165
  }
4114
4166
  return next;
4115
4167
  }
4116
- function injectStudioMotionScript(html, projectDir, activeCompositionPath) {
4168
+ function readStudioMotion(projectDir, activeCompositionPath) {
4117
4169
  const manifestContent = readStudioMotionManifestContent(projectDir);
4118
4170
  const script = createStudioMotionRenderBodyScript(manifestContent, {
4119
4171
  activeCompositionPath
4120
4172
  });
4121
- if (!script) return html;
4122
- const withDependencies = injectStudioMotionDependencies(html, manifestContent);
4173
+ return script ? { manifestContent, script } : null;
4174
+ }
4175
+ function injectStudioMotionScript(html, script) {
4123
4176
  const tag = `<script type="${AFTER_FONTS_SCRIPT_TYPE}">${script.replace(/<\/script/gi, "<\\/script")}</script>`;
4124
- return insertBeforeCloseTag2(withDependencies, "body", tag) ?? `${withDependencies}${tag}`;
4177
+ return insertBeforeCloseTag3(html, "body", tag) ?? `${html}${tag}`;
4125
4178
  }
4126
4179
  var GSAP_FALLBACK_ATTR = "data-hf-gsap-fallback";
4127
- var GSAP_CDN_FALLBACK_SCRIPT = `<script ${GSAP_FALLBACK_ATTR}>
4180
+ function gsapCdnFallbackScript(baseUrl) {
4181
+ return `<script ${GSAP_FALLBACK_ATTR}>
4128
4182
  (function(){
4129
- var cdnBase="${gsapCdnDist2()}";
4183
+ var cdnBase=${JSON.stringify(gsapCdnDist2(void 0, baseUrl)).replace(/</g, "\\u003c")};
4130
4184
  var loaded={};
4131
4185
  function loadFallback(file){
4132
4186
  if(loaded[file])return loaded[file];
@@ -4146,10 +4200,12 @@ var GSAP_CDN_FALLBACK_SCRIPT = `<script ${GSAP_FALLBACK_ATTR}>
4146
4200
  },true);
4147
4201
  })();
4148
4202
  </script>`;
4149
- function injectGsapCdnFallback(html) {
4203
+ }
4204
+ function injectGsapCdnFallback(html, baseUrl) {
4150
4205
  if (html.includes(GSAP_FALLBACK_ATTR)) return html;
4151
- if (html.includes("<head>")) return html.replace("<head>", "<head>" + GSAP_CDN_FALLBACK_SCRIPT);
4152
- return GSAP_CDN_FALLBACK_SCRIPT + html;
4206
+ if (html.includes("<head>"))
4207
+ return html.replace("<head>", "<head>" + gsapCdnFallbackScript(baseUrl));
4208
+ return gsapCdnFallbackScript(baseUrl) + html;
4153
4209
  }
4154
4210
  function parsePreviewVariablesParam(raw) {
4155
4211
  if (raw === void 0 || raw === "") return { ok: true, values: null };
@@ -4176,23 +4232,32 @@ function previewVariablesFromRequest(rawVariables) {
4176
4232
  var PREVIEW_CAPTURE_PARAM = "hf-capture";
4177
4233
  var EARLY_ERRORS_SCRIPT = `<script>(function(){var seen=window.${STUDIO_PREVIEW_ERRORS}=[];
4178
4234
  addEventListener("error",function(e){seen.push(e.message||String(e))});})();</script>`;
4179
- function injectStudioPreviewAugmentations(html, adapter, projectDir, activeCompositionPath, capture) {
4180
- const marked = capture ? injectTagsAtHeadStart(
4235
+ function injectStudioPreviewAugmentations(html, adapter, projectDir, activeCompositionPath, options) {
4236
+ const marked = options.capture ? injectTagsAtHeadStart(
4181
4237
  html,
4182
4238
  `<meta name="${STUDIO_PREVIEW_MARK_META}" content="${STUDIO_PREVIEW_CAPTURE_MODE}">`
4183
4239
  ) : injectTagsAtHeadStart(
4184
4240
  lazyPreviewImages(html),
4185
4241
  `<meta name="${STUDIO_PREVIEW_MARK_META}">${EARLY_ERRORS_SCRIPT}`
4186
4242
  );
4187
- return injectStudioMotionScript(
4188
- injectMotionPathPluginIfNeeded(
4189
- injectGsapCdnFallback(
4190
- injectProjectSignature(marked, resolveProjectSignature(adapter, projectDir))
4191
- )
4243
+ const withFallback = injectGsapCdnFallback(
4244
+ injectProjectSignature(
4245
+ injectTagsAtHeadStart(
4246
+ marked,
4247
+ `<meta name="${GSAP_BASE_URL_META}" content="${escapeGsapUrl(options.gsapBaseUrl)}">`
4248
+ ),
4249
+ resolveProjectSignature(adapter, projectDir)
4192
4250
  ),
4193
- projectDir,
4194
- activeCompositionPath
4251
+ options.gsapBaseUrl
4195
4252
  );
4253
+ const studioMotion = readStudioMotion(projectDir, activeCompositionPath);
4254
+ const withMotionDependencies = studioMotion ? injectStudioMotionDependencies(
4255
+ withFallback,
4256
+ studioMotion.manifestContent,
4257
+ options.gsapBaseUrl
4258
+ ) : withFallback;
4259
+ const withPlugin = injectMotionPathPluginIfNeeded(withMotionDependencies, options.gsapBaseUrl);
4260
+ return studioMotion ? injectStudioMotionScript(withPlugin, studioMotion.script) : withPlugin;
4196
4261
  }
4197
4262
  async function transformPreviewHtml(html, adapter, project, activeCompositionPath) {
4198
4263
  if (!adapter.transformPreviewHtml) return html;
@@ -4234,7 +4299,23 @@ var PREVIEW_BUNDLE_OPTIONS = {
4234
4299
  };
4235
4300
  var previewBaseHref = (projectId) => `/api/projects/${encodeURIComponent(projectId)}/preview/`;
4236
4301
  var withPreviewBase = (html, projectId) => hasBaseElement(html) ? html : injectTagsAtHeadStart(html, `<base href="${previewBaseHref(projectId)}">`);
4302
+ function resolvePreviewGsapBaseUrl(adapter) {
4303
+ const configured = process.env.HYPERFRAMES_GSAP_BASE_URL;
4304
+ if (configured?.trim() || !adapter.gsapLocalRoot) {
4305
+ try {
4306
+ return normalizeGsapBaseUrl(configured);
4307
+ } catch (cause) {
4308
+ throw new Error(
4309
+ "Invalid HYPERFRAMES_GSAP_BASE_URL: use an absolute HTTPS npm mirror root (plain HTTP only for localhost) without credentials, query, or fragment",
4310
+ { cause }
4311
+ );
4312
+ }
4313
+ }
4314
+ return normalizeGsapLocalRoot(adapter.gsapLocalRoot);
4315
+ }
4237
4316
  function registerPreviewRoutes(api, adapter) {
4317
+ const gsapBaseUrl = resolvePreviewGsapBaseUrl(adapter);
4318
+ const gsapEtagSalt = `:gsap:${createHash2("sha1").update(gsapBaseUrl).digest("hex").slice(0, 12)}`;
4238
4319
  const previewCacheHeaders = (etag) => ({
4239
4320
  "Cache-Control": "private, no-cache",
4240
4321
  ETag: etag
@@ -4269,7 +4350,7 @@ function registerPreviewRoutes(api, adapter) {
4269
4350
  adapter,
4270
4351
  project.dir,
4271
4352
  mainCompositionPath,
4272
- capture
4353
+ { capture, gsapBaseUrl }
4273
4354
  );
4274
4355
  if (previewVariables) bundled = injectPreviewVariables(bundled, previewVariables);
4275
4356
  bundled = await injectMediaCodecMap(
@@ -4297,7 +4378,7 @@ function registerPreviewRoutes(api, adapter) {
4297
4378
  adapter,
4298
4379
  project.dir,
4299
4380
  fallback.compositionPath,
4300
- capture
4381
+ { capture, gsapBaseUrl }
4301
4382
  );
4302
4383
  if (previewVariables) {
4303
4384
  fallbackAugmented = injectPreviewVariables(fallbackAugmented, previewVariables);
@@ -4322,7 +4403,7 @@ function registerPreviewRoutes(api, adapter) {
4322
4403
  if (vars.error !== void 0) return c.json({ error: vars.error }, 400);
4323
4404
  const previewVariables = vars.values;
4324
4405
  const capture = c.req.query(PREVIEW_CAPTURE_PARAM) !== void 0;
4325
- const etag = `"preview:${signature}${variablesEtagSalt(vars.raw)}${capture ? ":capture" : ""}"`;
4406
+ const etag = `"preview:${signature}${gsapEtagSalt}${variablesEtagSalt(vars.raw)}${capture ? ":capture" : ""}"`;
4326
4407
  const ifNoneMatch = c.req.header("If-None-Match");
4327
4408
  if (ifNoneMatch === etag) {
4328
4409
  return new Response(null, {
@@ -4372,7 +4453,7 @@ ${etag}`;
4372
4453
  }
4373
4454
  const compPathHash = createHash2("sha1").update(compPath).digest("hex");
4374
4455
  const capture = c.req.query(PREVIEW_CAPTURE_PARAM) !== void 0;
4375
- const etag = `"comp:v2:${compPathHash}:${signature}${variablesEtagSalt(vars.raw)}${capture ? ":capture" : ""}"`;
4456
+ const etag = `"comp:v2:${compPathHash}:${signature}${gsapEtagSalt}${variablesEtagSalt(vars.raw)}${capture ? ":capture" : ""}"`;
4376
4457
  const ifNoneMatch = c.req.header("If-None-Match");
4377
4458
  if (ifNoneMatch === etag) {
4378
4459
  return new Response(null, {
@@ -4388,12 +4469,16 @@ ${etag}`;
4388
4469
  compPath,
4389
4470
  adapter.runtimeUrl,
4390
4471
  baseHref,
4391
- stamped
4472
+ stamped,
4473
+ gsapBaseUrl
4392
4474
  );
4393
4475
  if (!html) return c.text("not found", 404);
4394
4476
  recordPreviewReferences(project.dir, html);
4395
4477
  html = ensureHfIds2(await transformPreviewHtml(html, adapter, project, compPath));
4396
- html = injectStudioPreviewAugmentations(html, adapter, project.dir, compPath, capture);
4478
+ html = injectStudioPreviewAugmentations(html, adapter, project.dir, compPath, {
4479
+ capture,
4480
+ gsapBaseUrl
4481
+ });
4397
4482
  if (previewVariables) html = injectPreviewVariables(html, previewVariables);
4398
4483
  html = await injectMediaCodecMap(html, adapter, project.dir, compPath, mediaCodecProbeCache);
4399
4484
  return c.html(html, 200, previewCacheHeaders(etag));
@@ -135,6 +135,7 @@ interface StudioApiAdapter {
135
135
  lintProject?: (projectDir: string) => Promise<ProjectLintResult> | ProjectLintResult;
136
136
  /** URL to the hyperframe runtime JS (injected into preview HTML). */
137
137
  runtimeUrl: string;
138
+ gsapLocalRoot?: `/${string}`;
138
139
  /**
139
140
  * Optional: post-process preview HTML before Studio augments it.
140
141
  * Useful when preview must mirror render-time compilation steps.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio-server",
3
- "version": "0.8.193",
3
+ "version": "0.8.195",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -84,8 +84,8 @@
84
84
  "linkedom": "^0.18.12",
85
85
  "postcss": "^8.5.8",
86
86
  "postcss-selector-parser": "^7.1.2",
87
- "@hyperframes/core": "0.8.193",
88
- "@hyperframes/parsers": "0.8.193"
87
+ "@hyperframes/parsers": "0.8.195",
88
+ "@hyperframes/core": "0.8.195"
89
89
  },
90
90
  "devDependencies": {
91
91
  "@types/node": "^25.0.10",