@hyperframes/studio-server 0.8.24 → 0.8.26

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
@@ -160,6 +160,7 @@ function collectProjectSignatureFiles(projectDir, dir, files) {
160
160
  files.push({
161
161
  file,
162
162
  mtimeMs: stat.mtimeMs,
163
+ ctimeMs: stat.ctimeMs,
163
164
  size: stat.size,
164
165
  textContentEligible: isTextContentEligible(file, stat.size)
165
166
  });
@@ -181,6 +182,7 @@ function collectProjectSignatureManifestFiles(projectDir, files) {
181
182
  files.push({
182
183
  file,
183
184
  mtimeMs: stat.mtimeMs,
185
+ ctimeMs: stat.ctimeMs,
184
186
  size: stat.size,
185
187
  textContentEligible: isTextContentEligible(file, stat.size)
186
188
  });
@@ -196,6 +198,8 @@ function createProjectFingerprint(projectDir, files) {
196
198
  hash.update("\0");
197
199
  hash.update(String(entry.mtimeMs));
198
200
  hash.update("\0");
201
+ hash.update(String(entry.ctimeMs));
202
+ hash.update("\0");
199
203
  hash.update(entry.textContentEligible ? "text" : "binary");
200
204
  hash.update("\0");
201
205
  }
@@ -1443,6 +1447,9 @@ function validateGsapMutationRequest(c, body) {
1443
1447
  if (!body || typeof body !== "object" || !("type" in body) || !body.type) {
1444
1448
  return c.json({ error: "mutation type required" }, 400);
1445
1449
  }
1450
+ if (containsRawGsapExpression(body)) {
1451
+ return c.json({ error: "raw JavaScript expressions are not accepted" }, 400);
1452
+ }
1446
1453
  const unsafeFields = findUnsafeMutationValues(body);
1447
1454
  if (unsafeFields.length > 0) return rejectUnsafeMutationValues(c, unsafeFields);
1448
1455
  if (body.type === "shift-positions-batch" && (!("shifts" in body) || !Array.isArray(body.shifts))) {
@@ -1450,6 +1457,12 @@ function validateGsapMutationRequest(c, body) {
1450
1457
  }
1451
1458
  return null;
1452
1459
  }
1460
+ function containsRawGsapExpression(value) {
1461
+ if (typeof value === "string") return value.startsWith("__raw:");
1462
+ if (Array.isArray(value)) return value.some(containsRawGsapExpression);
1463
+ if (!value || typeof value !== "object") return false;
1464
+ return Object.values(value).some(containsRawGsapExpression);
1465
+ }
1453
1466
  async function prepareGsapMutationScript(c, res, firstMutation) {
1454
1467
  const beforeHtml = readFileSync5(res.absPath, "utf-8");
1455
1468
  let html = beforeHtml;
@@ -2748,27 +2761,32 @@ function registerFileRoutes(api, adapter) {
2748
2761
  parsed.body.operations
2749
2762
  );
2750
2763
  if (patched === originalContent) {
2764
+ const version2 = fileContentVersion(originalContent);
2765
+ c.header("ETag", version2);
2751
2766
  return c.json({
2752
2767
  ok: true,
2753
2768
  changed: false,
2754
2769
  matched,
2755
2770
  content: originalContent,
2756
- path: ctx.filePath
2771
+ path: ctx.filePath,
2772
+ version: version2
2757
2773
  });
2758
2774
  }
2759
- const { backupPath } = writeMutationResult(
2775
+ const { backupPath, version } = writeMutationResult(
2760
2776
  c,
2761
2777
  ctx.project.dir,
2762
2778
  ctx.filePath,
2763
2779
  ctx.absPath,
2764
2780
  patched
2765
2781
  );
2782
+ c.header("ETag", version);
2766
2783
  return c.json({
2767
2784
  ok: true,
2768
2785
  changed: true,
2769
2786
  matched,
2770
2787
  content: patched,
2771
2788
  path: ctx.filePath,
2789
+ version,
2772
2790
  backupPath
2773
2791
  });
2774
2792
  });
@@ -4178,8 +4196,9 @@ function registerThumbnailRoutes(api, adapter) {
4178
4196
  if (!adapter.generateThumbnail) {
4179
4197
  return c.json({ error: "Thumbnails not available" }, 501);
4180
4198
  }
4181
- const project = await adapter.resolveProject(c.req.param("id"));
4182
- if (!project) return c.json({ error: "not found" }, 404);
4199
+ const resolved = await resolveProjectAndSignature(adapter, c.req.param("id"));
4200
+ if (!resolved) return c.json({ error: "not found" }, 404);
4201
+ const { project, signature: projectSignature } = resolved;
4183
4202
  let compPath = decodeURIComponent(
4184
4203
  c.req.path.replace(`/projects/${project.id}/thumbnail/`, "").split("?")[0] ?? ""
4185
4204
  );
@@ -4232,6 +4251,7 @@ function registerThumbnailRoutes(api, adapter) {
4232
4251
  const cacheDir = join11(project.dir, ".thumbnails");
4233
4252
  const selectorKey = selector ? `_${selector.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 80)}_${selectorIndex ?? 0}` : "";
4234
4253
  const urlVersionKey = urlVersion ? `_${urlVersion.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32)}` : "";
4254
+ const projectSignatureKey = `_${createHash4("sha1").update(projectSignature).digest("hex").slice(0, 16)}`;
4235
4255
  const outputScale = outputMode === "source" ? 1 : outputMode === "storyboard" ? Math.min(
4236
4256
  1,
4237
4257
  STORYBOARD_MAX_OUTPUT_DIMENSION / compW,
@@ -4239,7 +4259,7 @@ function registerThumbnailRoutes(api, adapter) {
4239
4259
  ) : Math.min(1, THUMBNAIL_MAX_OUTPUT_WIDTH / compW, THUMBNAIL_MAX_OUTPUT_HEIGHT / compH);
4240
4260
  const outputWidth = Math.max(1, Math.round(compW * outputScale));
4241
4261
  const outputHeight = Math.max(1, Math.round(compH * outputScale));
4242
- const cacheKey = `${THUMBNAIL_CACHE_VERSION}${urlVersionKey}${manualEditsKey}${motionKey}${sourceKey}_${format}_${outputMode}_${compPath.replace(/\//g, "_")}_${compW}x${compH}_${outputWidth}x${outputHeight}_${sourceMtime}_${seekTime.toFixed(2)}${selectorKey}.${format === "png" ? "png" : "jpg"}`;
4262
+ const cacheKey = `${THUMBNAIL_CACHE_VERSION}${urlVersionKey}${projectSignatureKey}${manualEditsKey}${motionKey}${sourceKey}_${format}_${outputMode}_${compPath.replace(/\//g, "_")}_${compW}x${compH}_${outputWidth}x${outputHeight}_${sourceMtime}_${seekTime.toFixed(2)}${selectorKey}.${format === "png" ? "png" : "jpg"}`;
4243
4263
  const cachePath = join11(cacheDir, cacheKey);
4244
4264
  if (!prunedCacheDirs.has(cacheDir)) {
4245
4265
  prunedCacheDirs.add(cacheDir);
@@ -4273,6 +4293,11 @@ function registerThumbnailRoutes(api, adapter) {
4273
4293
  signal
4274
4294
  });
4275
4295
  if (!generated) return null;
4296
+ const afterGeneration = await resolveProjectAndSignature(adapter, project.id);
4297
+ const freshSignature = createProjectSignature(project.dir);
4298
+ if (!afterGeneration || afterGeneration.project.dir !== project.dir || afterGeneration.signature !== projectSignature || freshSignature !== projectSignature) {
4299
+ return generated;
4300
+ }
4276
4301
  if (!existsSync8(cacheDir)) mkdirSync5(cacheDir, { recursive: true });
4277
4302
  writeThumbnailAtomically(cachePath, generated);
4278
4303
  return generated;