@hyperframes/studio-server 0.8.40 → 0.8.42

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
@@ -1150,7 +1150,7 @@ function writeFileWithReceipt(c, filePath, absPath, html) {
1150
1150
  }
1151
1151
  function writeMutationResult(c, projectDir, filePath, absPath, html) {
1152
1152
  const backup = snapshotBeforeWrite(projectDir, absPath);
1153
- if (backup.error) console.warn(`Failed to create backup for ${filePath}: ${backup.error}`);
1153
+ if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
1154
1154
  const { version } = writeFileWithReceipt(c, filePath, absPath, html);
1155
1155
  return { backupPath: backupPathForResponse(projectDir, backup.backupPath), version };
1156
1156
  }
@@ -1158,7 +1158,9 @@ function writeIfChanged(c, projectDir, filePath, absPath, original, next) {
1158
1158
  if (next === original) {
1159
1159
  return c.json({ ok: true, changed: false, content: original, path: filePath });
1160
1160
  }
1161
- const { backupPath } = writeMutationResult(c, projectDir, filePath, absPath, next);
1161
+ const mutationResult = writeMutationResult(c, projectDir, filePath, absPath, next);
1162
+ if (mutationResult instanceof Response) return mutationResult;
1163
+ const { backupPath } = mutationResult;
1162
1164
  return c.json({
1163
1165
  ok: true,
1164
1166
  changed: true,
@@ -1573,13 +1575,15 @@ async function applyGsapMutations(c, res, mutations) {
1573
1575
  return c.json({ error: "file changed during GSAP mutation", conflict: true }, 409);
1574
1576
  }
1575
1577
  if (changed) {
1576
- backupPath = writeMutationResult(
1578
+ const mutationResult = writeMutationResult(
1577
1579
  c,
1578
1580
  res.project.dir,
1579
1581
  res.filePath,
1580
1582
  res.absPath,
1581
1583
  newHtml
1582
- ).backupPath;
1584
+ );
1585
+ if (mutationResult instanceof Response) return mutationResult;
1586
+ backupPath = mutationResult.backupPath;
1583
1587
  }
1584
1588
  const responsePayload = {
1585
1589
  ok: true,
@@ -2476,8 +2480,7 @@ function registerFileRoutes(api, adapter) {
2476
2480
  );
2477
2481
  }
2478
2482
  backup = snapshotBeforeWrite(res.project.dir, res.absPath);
2479
- if (backup.error)
2480
- console.warn(`Failed to create backup for ${res.filePath}: ${backup.error}`);
2483
+ if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
2481
2484
  ftruncateSync(fd, 0);
2482
2485
  writeSync(fd, body, 0, body.length, 0);
2483
2486
  } finally {
@@ -2516,7 +2519,7 @@ function registerFileRoutes(api, adapter) {
2516
2519
  if ("error" in res) return res.error;
2517
2520
  const stat = statSync2(res.absPath);
2518
2521
  const backup = snapshotBeforeWrite(res.project.dir, res.absPath);
2519
- if (backup.error) console.warn(`Failed to create backup for ${res.filePath}: ${backup.error}`);
2522
+ if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
2520
2523
  if (stat.isDirectory()) {
2521
2524
  rmSync3(res.absPath, { recursive: true });
2522
2525
  } else {
@@ -2786,13 +2789,15 @@ function registerFileRoutes(api, adapter) {
2786
2789
  version: version2
2787
2790
  });
2788
2791
  }
2789
- const { version, backupPath } = writeMutationResult(
2792
+ const mutationResult = writeMutationResult(
2790
2793
  c,
2791
2794
  ctx.project.dir,
2792
2795
  ctx.filePath,
2793
2796
  ctx.absPath,
2794
2797
  result.html
2795
2798
  );
2799
+ if (mutationResult instanceof Response) return mutationResult;
2800
+ const { version, backupPath } = mutationResult;
2796
2801
  c.header("ETag", version);
2797
2802
  return c.json({
2798
2803
  ok: true,
@@ -2839,13 +2844,15 @@ function registerFileRoutes(api, adapter) {
2839
2844
  version: version2
2840
2845
  });
2841
2846
  }
2842
- const { backupPath, version } = writeMutationResult(
2847
+ const mutationResult = writeMutationResult(
2843
2848
  c,
2844
2849
  ctx.project.dir,
2845
2850
  ctx.filePath,
2846
2851
  ctx.absPath,
2847
2852
  patched
2848
2853
  );
2854
+ if (mutationResult instanceof Response) return mutationResult;
2855
+ const { backupPath, version } = mutationResult;
2849
2856
  c.header("ETag", version);
2850
2857
  return c.json({
2851
2858
  ok: true,
@@ -2940,13 +2947,15 @@ function registerFileRoutes(api, adapter) {
2940
2947
  result.error === "grouped elements must share a single parent" ? 422 : 400
2941
2948
  );
2942
2949
  }
2943
- const { backupPath } = writeMutationResult(
2950
+ const mutationResult = writeMutationResult(
2944
2951
  c,
2945
2952
  ctx.project.dir,
2946
2953
  ctx.filePath,
2947
2954
  ctx.absPath,
2948
2955
  result.html
2949
2956
  );
2957
+ if (mutationResult instanceof Response) return mutationResult;
2958
+ const { backupPath } = mutationResult;
2950
2959
  return c.json({
2951
2960
  ok: true,
2952
2961
  changed: true,
@@ -3692,7 +3701,8 @@ ${runtimeTag}`;
3692
3701
  if (!compFile || !existsSync6(compFile) || !statSync3(compFile).isFile()) {
3693
3702
  return c.text("not found", 404);
3694
3703
  }
3695
- const etag = `"comp:v2:${compPath}:${signature}${variablesEtagSalt(vars.raw)}"`;
3704
+ const compPathHash = createHash3("sha1").update(compPath).digest("hex");
3705
+ const etag = `"comp:v2:${compPathHash}:${signature}${variablesEtagSalt(vars.raw)}"`;
3696
3706
  const ifNoneMatch = c.req.header("If-None-Match");
3697
3707
  if (ifNoneMatch === etag) {
3698
3708
  return new Response(null, {
@@ -3878,6 +3888,14 @@ import { join as join10 } from "path";
3878
3888
  import { VALID_CANVAS_RESOLUTIONS } from "@hyperframes/parsers";
3879
3889
  import { formatRenderOutputTimestamp, parseFps } from "@hyperframes/core";
3880
3890
  var VALID_RESOLUTIONS = new Set(VALID_CANVAS_RESOLUTIONS);
3891
+ function contentDispositionHeader(disposition, filename) {
3892
+ const fallback = filename.replace(/[^\x20-\x7e]|["\\]/g, "_");
3893
+ const encoded = encodeURIComponent(filename).replace(
3894
+ /[!'()*]/g,
3895
+ (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`
3896
+ );
3897
+ return `${disposition}; filename="${fallback}"; filename*=UTF-8''${encoded}`;
3898
+ }
3881
3899
  function registerRenderRoutes(api, adapter) {
3882
3900
  const renderJobs = /* @__PURE__ */ new Map();
3883
3901
  const TTL_MS = 3e5;
@@ -4007,7 +4025,7 @@ function registerRenderRoutes(api, adapter) {
4007
4025
  return new Response(content, {
4008
4026
  headers: {
4009
4027
  "Content-Type": contentType,
4010
- "Content-Disposition": `inline; filename="${filename}"`,
4028
+ "Content-Disposition": contentDispositionHeader("inline", filename),
4011
4029
  "Accept-Ranges": "bytes",
4012
4030
  "Content-Length": String(content.length)
4013
4031
  }
@@ -4025,7 +4043,7 @@ function registerRenderRoutes(api, adapter) {
4025
4043
  return new Response(content, {
4026
4044
  headers: {
4027
4045
  "Content-Type": contentType,
4028
- "Content-Disposition": `attachment; filename="${filename}"`
4046
+ "Content-Disposition": contentDispositionHeader("attachment", filename)
4029
4047
  }
4030
4048
  });
4031
4049
  });
@@ -4058,7 +4076,7 @@ function registerRenderRoutes(api, adapter) {
4058
4076
  return new Response(content, {
4059
4077
  headers: {
4060
4078
  "Content-Type": contentType,
4061
- "Content-Disposition": `inline; filename="${filename}"`,
4079
+ "Content-Disposition": contentDispositionHeader("inline", filename),
4062
4080
  "Accept-Ranges": "bytes",
4063
4081
  "Content-Length": String(content.length)
4064
4082
  }