@hyperframes/studio-server 0.8.116 → 0.8.118

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/index.js CHANGED
@@ -3258,6 +3258,7 @@ import {
3258
3258
  stripEmbeddedRuntimeScripts as stripEmbeddedRuntimeScripts2
3259
3259
  } from "@hyperframes/core/compiler";
3260
3260
  import { STUDIO_PREVIEW_MARK_META } from "@hyperframes/core/studio-preview-mark";
3261
+ import { gsapCdnDist as gsapCdnDist2, motionPathPluginUrl } from "@hyperframes/core/gsap-cdn";
3261
3262
  import { injectTagsAtHeadStart } from "@hyperframes/core/compiler/html-document";
3262
3263
  import { isWithinProjectRoot } from "@hyperframes/parsers/asset-resolution";
3263
3264
 
@@ -3272,6 +3273,7 @@ import {
3272
3273
  } from "@hyperframes/core";
3273
3274
  import { stripEmbeddedRuntimeScripts } from "@hyperframes/core/compiler";
3274
3275
  import { isFullHtmlDocument } from "@hyperframes/core/compiler/html-document";
3276
+ import { gsapCdnDist } from "@hyperframes/core/gsap-cdn";
3275
3277
  function rewriteRelativePaths(root, compPath, projectDir) {
3276
3278
  const assetExists = (path) => existsSync4(join6(projectDir, path));
3277
3279
  rewriteAssetPaths(
@@ -3421,7 +3423,7 @@ ${compHeadContent}`;
3421
3423
  }
3422
3424
  if (!headContent.includes("gsap")) {
3423
3425
  headContent += `
3424
- <script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>`;
3426
+ <script src="${gsapCdnDist()}gsap.min.js"></script>`;
3425
3427
  }
3426
3428
  const htmlOpen = htmlAttrs ? `<html ${htmlAttrs}>` : "<html>";
3427
3429
  const bodyOpen = bodyAttrs ? `<body ${bodyAttrs}>` : "<body>";
@@ -3572,10 +3574,9 @@ function lazyPreviewImages(html) {
3572
3574
 
3573
3575
  // src/routes/preview.ts
3574
3576
  var PROJECT_SIGNATURE_META = "hyperframes-project-signature";
3575
- var GSAP_CDN_VERSION = "3.15.0";
3576
- var GSAP_CDN_SCRIPT = `<script src="https://cdn.jsdelivr.net/npm/gsap@${GSAP_CDN_VERSION}/dist/gsap.min.js"></script>`;
3577
- var GSAP_CUSTOM_EASE_CDN_SCRIPT = `<script src="https://cdn.jsdelivr.net/npm/gsap@${GSAP_CDN_VERSION}/dist/CustomEase.min.js"></script>`;
3578
- var GSAP_MOTION_PATH_CDN_SCRIPT = `<script src="https://cdn.jsdelivr.net/npm/gsap@${GSAP_CDN_VERSION}/dist/MotionPathPlugin.min.js"></script>`;
3577
+ var GSAP_CDN_SCRIPT = `<script src="${gsapCdnDist2()}gsap.min.js"></script>`;
3578
+ var GSAP_CUSTOM_EASE_CDN_SCRIPT = `<script src="${gsapCdnDist2()}CustomEase.min.js"></script>`;
3579
+ var GSAP_MOTION_PATH_CDN_SCRIPT = `<script src="${motionPathPluginUrl()}"></script>`;
3579
3580
  function injectProjectSignature(html, signature) {
3580
3581
  const tag = `<meta name="${PROJECT_SIGNATURE_META}" content="${signature}">`;
3581
3582
  if (html.includes(`name="${PROJECT_SIGNATURE_META}"`)) {
@@ -3632,8 +3633,8 @@ function injectMotionPathPluginIfNeeded(html) {
3632
3633
  const gsapScript = /<script\b[^>]*\bsrc=["'][^"']*\/gsap(\.min)?\.js["'][^>]*>\s*<\/script>/i;
3633
3634
  const match = html.match(gsapScript);
3634
3635
  if (match) {
3635
- const version = match[0].match(/gsap@([\d.]+)/)?.[1] ?? GSAP_CDN_VERSION;
3636
- const pluginTag = `<script src="https://cdn.jsdelivr.net/npm/gsap@${version}/dist/MotionPathPlugin.min.js"></script>`;
3636
+ const version = match[0].match(/gsap@([\d.]+)/)?.[1];
3637
+ const pluginTag = `<script src="${motionPathPluginUrl(version)}"></script>`;
3637
3638
  const end = html.indexOf(match[0]) + match[0].length;
3638
3639
  return html.slice(0, end) + "\n" + pluginTag + html.slice(end);
3639
3640
  }
@@ -3665,7 +3666,7 @@ function injectStudioMotionScript(html, projectDir, activeCompositionPath) {
3665
3666
  var GSAP_FALLBACK_ATTR = "data-hf-gsap-fallback";
3666
3667
  var GSAP_CDN_FALLBACK_SCRIPT = `<script ${GSAP_FALLBACK_ATTR}>
3667
3668
  (function(){
3668
- var cdnBase="https://cdn.jsdelivr.net/npm/gsap@${GSAP_CDN_VERSION}/dist/";
3669
+ var cdnBase="${gsapCdnDist2()}";
3669
3670
  var loaded={};
3670
3671
  function loadFallback(file){
3671
3672
  if(loaded[file])return loaded[file];
@@ -4890,7 +4891,7 @@ function registerWaveformRoutes(api, adapter) {
4890
4891
 
4891
4892
  // src/routes/freezeFrame.ts
4892
4893
  import { execFile } from "child_process";
4893
- import { existsSync as existsSync8, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
4894
+ import { closeSync as closeSync3, openSync as openSync3, readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync8 } from "fs";
4894
4895
  import { dirname as dirname5, join as join13, relative as relative4, sep as sep2 } from "path";
4895
4896
  import { findFfBinary as findFfBinary3 } from "@hyperframes/parsers/ff-binaries";
4896
4897
  import { replaceFileAtomically as replaceFileAtomically3 } from "@hyperframes/core/atomic-file";
@@ -4918,7 +4919,7 @@ function freezeFrameMediaTime(input) {
4918
4919
  return input.mediaStart + sourceTimeAt(rate, Math.max(0, input.playhead - input.clipStart));
4919
4920
  }
4920
4921
  function freezeExtractArgs(src, mediaTime, output) {
4921
- return ["-n", "-ss", String(round3(mediaTime)), "-i", src, "-frames:v", "1", output];
4922
+ return ["-y", "-ss", String(round3(mediaTime)), "-i", src, "-frames:v", "1", output];
4922
4923
  }
4923
4924
  var randomStillToken = () => randomBytes(4).toString("hex");
4924
4925
  function freezeStillFileName(clipId, playhead, token = randomStillToken()) {
@@ -5091,6 +5092,15 @@ function readExpected(absPath, expectedVersion) {
5091
5092
  }
5092
5093
  return fileContentVersion(content) === expectedVersion ? { content } : { error: "file conflict", status: 409 };
5093
5094
  }
5095
+ function claimStillNameBeforeExtracting(path) {
5096
+ try {
5097
+ closeSync3(openSync3(path, "wx"));
5098
+ return true;
5099
+ } catch (error) {
5100
+ if (error.code === "EEXIST") return false;
5101
+ throw error;
5102
+ }
5103
+ }
5094
5104
  async function extractStill(projectDir, absPath, source, playhead, tools) {
5095
5105
  const fileDir = dirname5(absPath);
5096
5106
  const mediaPath = pinWithinProject(projectDir, relative4(projectDir, join13(fileDir, source.src)));
@@ -5102,17 +5112,18 @@ async function extractStill(projectDir, absPath, source, playhead, tools) {
5102
5112
  if (!imagePath || dirname5(imagePath) !== freezeDir) {
5103
5113
  return { error: `forbidden freeze path: ${fileName}`, status: 403 };
5104
5114
  }
5105
- if (existsSync8(imagePath))
5115
+ if (!claimStillNameBeforeExtracting(imagePath))
5106
5116
  return { error: `freeze still already exists: ${fileName}`, status: 409 };
5107
5117
  const extracted = await tools.extract(freezeExtractArgs(mediaPath, source.mediaTime, imagePath));
5108
- if (!extracted.ok) {
5109
- return {
5110
- error: `Could not extract the frame: ${extracted.error ?? "ffmpeg failed"}`,
5111
- status: 500
5112
- };
5118
+ const partialOrNoFrame = !extracted.ok || statSync8(imagePath).size === 0;
5119
+ if (partialOrNoFrame) {
5120
+ rmSync5(imagePath, { force: true });
5121
+ const reason = extracted.ok ? "no frame at this time" : extracted.error ?? "ffmpeg failed";
5122
+ return { error: `Could not extract the frame: ${reason}`, status: 500 };
5113
5123
  }
5114
5124
  const depth = relative4(projectDir, fileDir).split(sep2).filter(Boolean).length;
5115
- return { imageSrc: `${"../".repeat(depth)}${FREEZE_DIR.join("/")}/${fileName}` };
5125
+ const stillPath = `${FREEZE_DIR.join("/")}/${fileName}`;
5126
+ return { imageSrc: `${"../".repeat(depth)}${stillPath}`, stillPath, imagePath };
5116
5127
  }
5117
5128
  function writeFolded(projectDir, absPath, path, before, after, options) {
5118
5129
  if (readFileSync11(absPath, "utf-8") !== before) {
@@ -5146,17 +5157,21 @@ function registerFreezeFrameRoutes(api, adapter, extract = ffmpegExtractor, stil
5146
5157
  stillToken
5147
5158
  });
5148
5159
  if ("error" in still) return c.json({ error: still.error }, still.status);
5149
- const { imageSrc } = still;
5160
+ const { imageSrc, stillPath, imagePath } = still;
5150
5161
  const folded = applyFreezeFrameToHtml(before, {
5151
5162
  target: body.target,
5152
5163
  playhead: body.playhead,
5153
5164
  imageSrc
5154
5165
  });
5155
- if (!folded) return c.json({ error: "Freeze target was not found in the file" }, 400);
5166
+ const failAndRemoveStill = (error, status) => {
5167
+ rmSync5(imagePath, { force: true });
5168
+ return c.json({ error }, status);
5169
+ };
5170
+ if (!folded) return failAndRemoveStill("Freeze target was not found in the file", 400);
5156
5171
  const written = writeFolded(project.dir, absPath, body.path, before, folded.html, {
5157
5172
  token: body.transactionToken ?? c.req.header("X-Hyperframes-Write-Token")
5158
5173
  });
5159
- if ("error" in written) return c.json({ error: written.error }, written.status);
5174
+ if ("error" in written) return failAndRemoveStill(written.error, written.status);
5160
5175
  const { version, writeToken, backupPath } = written;
5161
5176
  return c.json({
5162
5177
  ok: true,
@@ -5167,13 +5182,14 @@ function registerFreezeFrameRoutes(api, adapter, extract = ffmpegExtractor, stil
5167
5182
  writeToken,
5168
5183
  backupPath,
5169
5184
  freezeId: folded.freezeId,
5170
- imageSrc
5185
+ imageSrc,
5186
+ stillPath
5171
5187
  });
5172
5188
  });
5173
5189
  }
5174
5190
 
5175
5191
  // src/routes/loudness.ts
5176
- import { existsSync as existsSync9 } from "fs";
5192
+ import { existsSync as existsSync8 } from "fs";
5177
5193
  var defaultMeasure = (file, window) => measureAudio(requiredFfmpeg(), file, window);
5178
5194
  var InvalidField = class extends Error {
5179
5195
  };
@@ -5224,7 +5240,7 @@ function registerLoudnessRoutes(api, adapter, measure = defaultMeasure) {
5224
5240
  } catch (error) {
5225
5241
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
5226
5242
  }
5227
- if (!existsSync9(file)) return c.json({ error: "file not found" }, 404);
5243
+ if (!existsSync8(file)) return c.json({ error: "file not found" }, 404);
5228
5244
  try {
5229
5245
  const measurement = await measure(file, request.window);
5230
5246
  const plan = absoluteLoudnessPlan(
@@ -5239,7 +5255,7 @@ function registerLoudnessRoutes(api, adapter, measure = defaultMeasure) {
5239
5255
  }
5240
5256
 
5241
5257
  // src/routes/peaks.ts
5242
- import { existsSync as existsSync10, readFileSync as readFileSync12, statSync as statSync9 } from "fs";
5258
+ import { existsSync as existsSync9, readFileSync as readFileSync12, statSync as statSync9 } from "fs";
5243
5259
  import { join as join14, resolve as resolve6 } from "path";
5244
5260
  import { isWithinProjectRoot as isWithinProjectRoot3 } from "@hyperframes/parsers/asset-resolution";
5245
5261
 
@@ -5357,7 +5373,7 @@ function decodeOnce(cachePath, mediaPath, decode) {
5357
5373
  }
5358
5374
  function readCachedBins(cacheDir, cachePath) {
5359
5375
  try {
5360
- if (!isWaveformCacheDirectory(cacheDir) || !existsSync10(cachePath)) return null;
5376
+ if (!isWaveformCacheDirectory(cacheDir) || !existsSync9(cachePath)) return null;
5361
5377
  const parsed = JSON.parse(readFileSync12(cachePath, "utf-8"));
5362
5378
  return Array.isArray(parsed) && parsed.every((n) => typeof n === "number") ? parsed : null;
5363
5379
  } catch {
@@ -5393,7 +5409,7 @@ function registerPeakRoutes(api, adapter, decode = decodePeakMap) {
5393
5409
  }
5394
5410
 
5395
5411
  // src/routes/fonts.ts
5396
- import { closeSync as closeSync3, constants, fstatSync as fstatSync3, openSync as openSync3, readSync } from "fs";
5412
+ import { closeSync as closeSync4, constants, fstatSync as fstatSync3, openSync as openSync4, readSync } from "fs";
5397
5413
  import {
5398
5414
  collectFontFileEntries,
5399
5415
  fontDirectories,
@@ -5500,7 +5516,7 @@ function registerFontRoutes(api) {
5500
5516
  if (!located) return c.json({ error: "font not found" }, 404);
5501
5517
  let fd;
5502
5518
  try {
5503
- fd = openSync3(located.path, constants.O_RDONLY | constants.O_NOFOLLOW);
5519
+ fd = openSync4(located.path, constants.O_RDONLY | constants.O_NOFOLLOW);
5504
5520
  } catch {
5505
5521
  return c.json({ error: "font file not accessible" }, 404);
5506
5522
  }
@@ -5522,7 +5538,7 @@ function registerFontRoutes(api) {
5522
5538
  } catch {
5523
5539
  return c.json({ error: "failed to read font file" }, 500);
5524
5540
  } finally {
5525
- closeSync3(fd);
5541
+ closeSync4(fd);
5526
5542
  }
5527
5543
  });
5528
5544
  }
@@ -5658,7 +5674,7 @@ function registerSelectionRoutes(api, adapter) {
5658
5674
 
5659
5675
  // src/routes/media.ts
5660
5676
  import { streamSSE as streamSSE2 } from "hono/streaming";
5661
- import { existsSync as existsSync11 } from "fs";
5677
+ import { existsSync as existsSync10 } from "fs";
5662
5678
  import { basename as basename2, dirname as dirname6, extname, join as join15, posix } from "path";
5663
5679
  var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
5664
5680
  ".mp4",
@@ -5696,7 +5712,7 @@ function uniqueAssetPath(projectDir, assetPath, taken) {
5696
5712
  const ext = extname(assetPath);
5697
5713
  const withoutExt = assetPath.slice(0, -ext.length);
5698
5714
  let candidate = assetPath;
5699
- for (let index = 2; taken.has(candidate) || existsSync11(join15(projectDir, candidate)); index++) {
5715
+ for (let index = 2; taken.has(candidate) || existsSync10(join15(projectDir, candidate)); index++) {
5700
5716
  candidate = `${withoutExt}-${index}${ext}`;
5701
5717
  }
5702
5718
  return candidate;
@@ -5757,7 +5773,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
5757
5773
  }
5758
5774
  const filePath = resolveWithinProject(project.dir, assetPath);
5759
5775
  if (!filePath) return c.json({ error: "forbidden" }, 403);
5760
- if (!existsSync11(filePath)) return c.json({ error: "media not found" }, 404);
5776
+ if (!existsSync10(filePath)) return c.json({ error: "media not found" }, 404);
5761
5777
  return c.json({ path: assetPath, metadata: await readMediaMetadata(filePath) });
5762
5778
  });
5763
5779
  api.post(
@@ -5779,7 +5795,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
5779
5795
  }
5780
5796
  const inputPath = resolveWithinProject(project.dir, inputAssetPath);
5781
5797
  if (!inputPath) return c.json({ error: "forbidden" }, 403);
5782
- if (!existsSync11(inputPath)) return c.json({ error: "input media not found" }, 404);
5798
+ if (!existsSync10(inputPath)) return c.json({ error: "input media not found" }, 404);
5783
5799
  const inputIsVideo = isVideoPath(inputAssetPath);
5784
5800
  const inputIsImage = isImagePath(inputAssetPath);
5785
5801
  if (!inputIsVideo && !inputIsImage) {
@@ -5873,12 +5889,12 @@ function registerMediaRoutes(api, adapter, options = {}) {
5873
5889
  }
5874
5890
 
5875
5891
  // src/routes/globalAssets.ts
5876
- import { existsSync as existsSync12, readFileSync as readFileSync13 } from "fs";
5892
+ import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
5877
5893
  import { homedir } from "os";
5878
5894
  import { join as join16 } from "path";
5879
5895
  function readGlobalAssets(home = homedir()) {
5880
5896
  const manifestPath = join16(home, ".media", "manifest.jsonl");
5881
- if (!existsSync12(manifestPath)) return [];
5897
+ if (!existsSync11(manifestPath)) return [];
5882
5898
  const out = [];
5883
5899
  for (const line of readFileSync13(manifestPath, "utf8").split("\n")) {
5884
5900
  if (!line.trim()) continue;
@@ -6132,7 +6148,7 @@ function numericAttribute(element, name) {
6132
6148
 
6133
6149
  // src/helpers/previewDocumentStore.ts
6134
6150
  import { createHash as createHash6 } from "crypto";
6135
- import { readFileSync as readFileSync14, readdirSync as readdirSync5, rmSync as rmSync5 } from "fs";
6151
+ import { readFileSync as readFileSync14, readdirSync as readdirSync5, rmSync as rmSync6 } from "fs";
6136
6152
  import { join as join17 } from "path";
6137
6153
  import { replaceFileAtomically as replaceFileAtomically4 } from "@hyperframes/core/atomic-file";
6138
6154
  function createPreviewDocumentStore(projectDir, salt) {
@@ -6153,7 +6169,7 @@ ${key}`).digest("hex").slice(0, 32)}.html`);
6153
6169
  const file = fileFor(key);
6154
6170
  replaceFileAtomically4(file, html, 420);
6155
6171
  for (const name of readdirSync5(dir)) {
6156
- if (join17(dir, name) !== file) rmSync5(join17(dir, name), { force: true });
6172
+ if (join17(dir, name) !== file) rmSync6(join17(dir, name), { force: true });
6157
6173
  }
6158
6174
  } catch {
6159
6175
  }