@hyperframes/studio-server 0.8.222 → 0.8.224

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.
@@ -82,7 +82,40 @@ function parseSourceDocument(raw) {
82
82
  );
83
83
  return { document, wrappedFragment: !hasDocumentShell };
84
84
  }
85
+ function dropCssRulesForId(document, id) {
86
+ const idToken = `#${id}`;
87
+ const namesId = (selector) => {
88
+ let found = false;
89
+ selectorParser((selectors) => {
90
+ selectors.walkIds((node) => {
91
+ let up = node.parent;
92
+ while (up && !(up.type === "pseudo" && up.value === ":not")) up = up.parent;
93
+ if (node.value === id && !up) found = true;
94
+ });
95
+ }).processSync(selector);
96
+ return found;
97
+ };
98
+ for (const styleEl of document.querySelectorAll("style")) {
99
+ const css = styleEl.textContent ?? "";
100
+ if (!css.includes(idToken)) continue;
101
+ let root;
102
+ try {
103
+ root = postcss.parse(css);
104
+ } catch {
105
+ continue;
106
+ }
107
+ let dropped = false;
108
+ root.walkRules((rule) => {
109
+ if (rule.selectors.length > 0 && rule.selectors.every(namesId)) {
110
+ rule.remove();
111
+ dropped = true;
112
+ }
113
+ });
114
+ if (dropped) styleEl.textContent = root.toString();
115
+ }
116
+ }
85
117
  function duplicateCssRulesForId(document, originalId, newId) {
118
+ dropCssRulesForId(document, newId);
86
119
  const idToken = `#${originalId}`;
87
120
  const transform = selectorParser((selectors) => {
88
121
  selectors.walkIds((node) => {
@@ -165,7 +198,9 @@ function removeElementsFromHtml(source, targets) {
165
198
  for (const target of targets) {
166
199
  const element = findTargetElement(document, target);
167
200
  if (!element) continue;
201
+ const id = element.getAttribute("id");
168
202
  removeElementWithGsapCascade(document, element);
203
+ if (id && !document.getElementById(id)) dropCssRulesForId(document, id);
169
204
  removed = true;
170
205
  }
171
206
  if (!removed) return source;
@@ -847,7 +847,7 @@ import { dirname as dirname2, join as join4 } from "path";
847
847
 
848
848
  // src/history/historyId.ts
849
849
  import { randomUUID as randomUUID4 } from "crypto";
850
- import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync4, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
850
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync4, statSync as statSync2, utimesSync, writeFileSync as writeFileSync2 } from "fs";
851
851
  import { basename, join as join3, resolve as resolve4 } from "path";
852
852
  import { replaceFileAtomically as replaceFileAtomically3 } from "@hyperframes/core/atomic-file";
853
853
  var ID_PATH = join3(".hyperframes", "history-id");
@@ -909,8 +909,21 @@ var HistoryIdError = class extends Error {
909
909
  this.name = "HistoryIdError";
910
910
  }
911
911
  };
912
+ function markUsed(historyDir) {
913
+ const now = /* @__PURE__ */ new Date();
914
+ try {
915
+ utimesSync(join3(historyDir, "project.json"), now, now);
916
+ } catch {
917
+ return;
918
+ }
919
+ }
912
920
  function recordProject(historyDir, dir, folder) {
913
921
  const record = { dir, ino: folder.ino, born: folder.birthtimeMs, dev: folder.dev };
922
+ const was = readRecord(historyDir);
923
+ if (was?.dir === record.dir && was.ino === record.ino && was.born === record.born && was.dev === record.dev) {
924
+ markUsed(historyDir);
925
+ return;
926
+ }
914
927
  mkdirSync2(historyDir, { recursive: true });
915
928
  replaceFileAtomically3(join3(historyDir, "project.json"), JSON.stringify(record), 420);
916
929
  }
@@ -13,7 +13,7 @@ import {
13
13
  splitElementInHtml,
14
14
  unwrapElementsFromHtml,
15
15
  wrapElementsInHtml
16
- } from "../chunk-V6K3WWWU.js";
16
+ } from "../chunk-LRTNNDXS.js";
17
17
  export {
18
18
  dedupeClonedCompositionId,
19
19
  findTargetElement,
@@ -24,7 +24,7 @@ import {
24
24
  recordRenderSuccess,
25
25
  registerRenderOutput,
26
26
  retainSnapshotOutput
27
- } from "../chunk-CZ2QLV2C.js";
27
+ } from "../chunk-UC72OCMB.js";
28
28
  import "../chunk-QF7MFJJU.js";
29
29
  import "../chunk-SVQFE44C.js";
30
30
  import "../chunk-L3HJVTQE.js";
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  splitElementInHtml,
18
18
  unwrapElementsFromHtml,
19
19
  wrapElementsInHtml
20
- } from "./chunk-V6K3WWWU.js";
20
+ } from "./chunk-LRTNNDXS.js";
21
21
  import {
22
22
  isCompositionSource,
23
23
  stampProjectHfIds
@@ -66,7 +66,7 @@ import {
66
66
  retainSnapshotOutput,
67
67
  serveWhileOpen,
68
68
  settledFileTag
69
- } from "./chunk-CZ2QLV2C.js";
69
+ } from "./chunk-UC72OCMB.js";
70
70
  import {
71
71
  clearElementScreenshotIsolation,
72
72
  getElementScreenshotClip
@@ -4760,8 +4760,17 @@ function registerLintRoutes(api, adapter) {
4760
4760
 
4761
4761
  // src/routes/render.ts
4762
4762
  import { streamSSE } from "hono/streaming";
4763
- import { existsSync as existsSync6, lstatSync as lstatSync3, readFileSync as readFileSync8, unlinkSync as unlinkSync3, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
4763
+ import {
4764
+ createReadStream as createReadStream2,
4765
+ existsSync as existsSync6,
4766
+ lstatSync as lstatSync3,
4767
+ readFileSync as readFileSync8,
4768
+ unlinkSync as unlinkSync3,
4769
+ readdirSync as readdirSync3,
4770
+ statSync as statSync5
4771
+ } from "fs";
4764
4772
  import { dirname as dirname5, join as join10 } from "path";
4773
+ import { Readable as Readable2 } from "stream";
4765
4774
  import { VALID_CANVAS_RESOLUTIONS } from "@hyperframes/parsers";
4766
4775
  import { formatRenderOutputTimestamp, parseFps, fpsToNumber } from "@hyperframes/core";
4767
4776
  var VALID_RESOLUTIONS = new Set(VALID_CANVAS_RESOLUTIONS);
@@ -4925,23 +4934,25 @@ function registerRenderRoutes(api, adapter) {
4925
4934
  const ext = RENDER_EXTENSIONS.find((e) => filePath.endsWith(e));
4926
4935
  return (ext && RENDER_MIME[ext]) ?? "video/mp4";
4927
4936
  }
4937
+ function renderFileResponse(filePath, disposition, filename, method) {
4938
+ const headers = {
4939
+ "Content-Type": renderContentType(filePath),
4940
+ "Content-Disposition": contentDispositionHeader(disposition, filename),
4941
+ ...disposition === "inline" ? { "Accept-Ranges": "bytes" } : {},
4942
+ "Content-Length": String(statSync5(filePath).size)
4943
+ };
4944
+ if (method === "HEAD") return new Response(null, { headers });
4945
+ const body = Readable2.toWeb(createReadStream2(filePath));
4946
+ return new Response(body, { headers });
4947
+ }
4928
4948
  api.get("/render/:jobId/view", (c) => {
4929
4949
  const { jobId } = c.req.param();
4930
4950
  const job = renderJobs.get(jobId);
4931
4951
  if (!job?.outputPath || !isPlainFile(job.outputPath)) {
4932
4952
  return c.json({ error: "not found" }, 404);
4933
4953
  }
4934
- const contentType = renderContentType(job.outputPath);
4935
4954
  const filename = job.outputPath.split("/").pop() ?? `render.mp4`;
4936
- const content = readFileSync8(job.outputPath);
4937
- return new Response(content, {
4938
- headers: {
4939
- "Content-Type": contentType,
4940
- "Content-Disposition": contentDispositionHeader("inline", filename),
4941
- "Accept-Ranges": "bytes",
4942
- "Content-Length": String(content.length)
4943
- }
4944
- });
4955
+ return renderFileResponse(job.outputPath, "inline", filename, c.req.method);
4945
4956
  });
4946
4957
  api.get("/render/:jobId/download", (c) => {
4947
4958
  const { jobId } = c.req.param();
@@ -4949,15 +4960,8 @@ function registerRenderRoutes(api, adapter) {
4949
4960
  if (!job?.outputPath || !isPlainFile(job.outputPath)) {
4950
4961
  return c.json({ error: "not found" }, 404);
4951
4962
  }
4952
- const contentType = renderContentType(job.outputPath);
4953
4963
  const filename = job.outputPath.split("/").pop() ?? `render.mp4`;
4954
- const content = readFileSync8(job.outputPath);
4955
- return new Response(content, {
4956
- headers: {
4957
- "Content-Type": contentType,
4958
- "Content-Disposition": contentDispositionHeader("attachment", filename)
4959
- }
4960
- });
4964
+ return renderFileResponse(job.outputPath, "attachment", filename, c.req.method);
4961
4965
  });
4962
4966
  async function renderDirectories(jobId) {
4963
4967
  const outputPath = renderJobs.get(jobId)?.outputPath;
@@ -4988,16 +4992,7 @@ function registerRenderRoutes(api, adapter) {
4988
4992
  const fp = resolveWithinProject(rendersDir, filename);
4989
4993
  if (!fp || isPrivateProjectFile(project.dir, fp)) return c.json({ error: "forbidden" }, 403);
4990
4994
  if (!existsSync6(fp)) return c.json({ error: "not found" }, 404);
4991
- const contentType = renderContentType(fp);
4992
- const content = readFileSync8(fp);
4993
- return new Response(content, {
4994
- headers: {
4995
- "Content-Type": contentType,
4996
- "Content-Disposition": contentDispositionHeader("inline", filename),
4997
- "Accept-Ranges": "bytes",
4998
- "Content-Length": String(content.length)
4999
- }
5000
- });
4995
+ return renderFileResponse(fp, "inline", filename, c.req.method);
5001
4996
  });
5002
4997
  api.get("/projects/:id/renders", async (c) => {
5003
4998
  const project = await adapter.resolveProject(c.req.param("id"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio-server",
3
- "version": "0.8.222",
3
+ "version": "0.8.224",
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/parsers": "0.8.222",
88
- "@hyperframes/core": "0.8.222"
87
+ "@hyperframes/parsers": "0.8.224",
88
+ "@hyperframes/core": "0.8.224"
89
89
  },
90
90
  "devDependencies": {
91
91
  "@types/node": "^25.0.10",