@hyperframes/core 0.6.94 → 0.6.96

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.
Files changed (43) hide show
  1. package/dist/compiler/htmlBundler.d.ts.map +1 -1
  2. package/dist/compiler/htmlBundler.js +10 -7
  3. package/dist/compiler/htmlBundler.js.map +1 -1
  4. package/dist/generated/runtime-inline.js +1 -1
  5. package/dist/generated/runtime-inline.js.map +1 -1
  6. package/dist/hyperframe.manifest.json +1 -1
  7. package/dist/hyperframe.runtime.iife.js +21 -21
  8. package/dist/hyperframe.runtime.mjs +21 -21
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/media/gif.d.ts.map +1 -1
  14. package/dist/media/gif.js +9 -2
  15. package/dist/media/gif.js.map +1 -1
  16. package/dist/safePath.d.ts +24 -0
  17. package/dist/safePath.d.ts.map +1 -0
  18. package/dist/safePath.js +58 -0
  19. package/dist/safePath.js.map +1 -0
  20. package/dist/studio-api/helpers/backupJournal.d.ts +9 -0
  21. package/dist/studio-api/helpers/backupJournal.d.ts.map +1 -0
  22. package/dist/studio-api/helpers/backupJournal.js +81 -0
  23. package/dist/studio-api/helpers/backupJournal.js.map +1 -0
  24. package/dist/studio-api/helpers/finiteMutation.d.ts +11 -0
  25. package/dist/studio-api/helpers/finiteMutation.d.ts.map +1 -0
  26. package/dist/studio-api/helpers/finiteMutation.js +21 -0
  27. package/dist/studio-api/helpers/finiteMutation.js.map +1 -0
  28. package/dist/studio-api/helpers/safePath.d.ts +9 -2
  29. package/dist/studio-api/helpers/safePath.d.ts.map +1 -1
  30. package/dist/studio-api/helpers/safePath.js +21 -8
  31. package/dist/studio-api/helpers/safePath.js.map +1 -1
  32. package/dist/studio-api/routes/files.d.ts.map +1 -1
  33. package/dist/studio-api/routes/files.js +82 -10
  34. package/dist/studio-api/routes/files.js.map +1 -1
  35. package/dist/studio-api/routes/lint.d.ts.map +1 -1
  36. package/dist/studio-api/routes/lint.js +2 -2
  37. package/dist/studio-api/routes/lint.js.map +1 -1
  38. package/dist/studio-api/routes/projects.js +2 -2
  39. package/dist/studio-api/routes/projects.js.map +1 -1
  40. package/dist/studio-api/routes/render.d.ts.map +1 -1
  41. package/dist/studio-api/routes/render.js +6 -2
  42. package/dist/studio-api/routes/render.js.map +1 -1
  43. package/package.json +5 -1
package/dist/index.d.ts CHANGED
@@ -22,6 +22,7 @@ export { getHyperframeRuntimeScript } from "./generated/runtime-inline";
22
22
  export { buildHyperframesRuntimeScript, type HyperframesRuntimeBuildOptions, } from "./inline-scripts/hyperframesRuntime.engine";
23
23
  export { MEDIA_VISUAL_STYLE_PROPERTIES, copyMediaVisualStyles, quantizeTimeToFrame, type MediaVisualStyleProperty, } from "./inline-scripts/parityContract";
24
24
  export { redactTelemetryString } from "./telemetryRedaction";
25
+ export { isSafePath } from "./safePath";
25
26
  export type { HyperframePickerApi, HyperframePickerBoundingBox, HyperframePickerElementInfo, } from "./inline-scripts/pickerApi";
26
27
  export type { FrameAdapter, FrameAdapterContext } from "./adapters/types";
27
28
  export type { GSAPTimelineLike, CreateGSAPFrameAdapterOptions } from "./adapters/gsap";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ export { getHyperframeRuntimeScript } from "./generated/runtime-inline";
18
18
  export { buildHyperframesRuntimeScript, } from "./inline-scripts/hyperframesRuntime.engine";
19
19
  export { MEDIA_VISUAL_STYLE_PROPERTIES, copyMediaVisualStyles, quantizeTimeToFrame, } from "./inline-scripts/parityContract";
20
20
  export { redactTelemetryString } from "./telemetryRedaction";
21
+ export { isSafePath } from "./safePath";
21
22
  export { createGSAPFrameAdapter } from "./adapters/gsap";
22
23
  // Text measurement
23
24
  export { fitTextFontSize } from "./text/index.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAuCA,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,uBAAuB,EACvB,QAAQ,EACR,mBAAmB,EACnB,KAAK,EACL,WAAW,EACX,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,0BAA0B,EAC1B,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,cAAc,GACf,MAAM,cAAc,CAAC;AAEtB,YAAY;AACZ,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EACL,QAAQ,EACR,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,WAAW,EACX,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AAQ/B,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,EACvB,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,yBAAyB,CAAC;AAIjC,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,gBAAgB,EAChB,qBAAqB,EACrB,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,sBAAsB,CAAC;AAK9B,OAAO,EACL,uBAAuB,EACvB,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,0BAA0B,CAAC;AAUlC,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,cAAc,EACd,wBAAwB,GACzB,MAAM,2BAA2B,CAAC;AASnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAA4B,MAAM,aAAa,CAAC;AAEjF,iBAAiB;AACjB,OAAO,EACL,4BAA4B,EAC5B,2BAA2B,EAC3B,2BAA2B,GAE5B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,0BAA0B,GAE3B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EACL,6BAA6B,GAE9B,MAAM,4CAA4C,CAAC;AACpD,OAAO,EACL,6BAA6B,EAC7B,qBAAqB,EACrB,mBAAmB,GAEpB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAU7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,mBAAmB;AACnB,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGlD,qCAAqC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,2CAA2C;AAC3C,OAAO,EACL,iBAAiB,EACjB,6BAA6B,GAE9B,MAAM,gCAAgC,CAAC;AAiBxC,OAAO,EACL,UAAU,EACV,UAAU,EACV,cAAc,EACd,aAAa,EACb,WAAW,EACX,eAAe,GAChB,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAuCA,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,uBAAuB,EACvB,QAAQ,EACR,mBAAmB,EACnB,KAAK,EACL,WAAW,EACX,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,0BAA0B,EAC1B,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,cAAc,GACf,MAAM,cAAc,CAAC;AAEtB,YAAY;AACZ,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EACL,QAAQ,EACR,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,WAAW,EACX,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AAQ/B,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,EACvB,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,yBAAyB,CAAC;AAIjC,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,gBAAgB,EAChB,qBAAqB,EACrB,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,sBAAsB,CAAC;AAK9B,OAAO,EACL,uBAAuB,EACvB,0BAA0B,EAC1B,yBAAyB,GAC1B,MAAM,0BAA0B,CAAC;AAUlC,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,cAAc,EACd,wBAAwB,GACzB,MAAM,2BAA2B,CAAC;AASnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAA4B,MAAM,aAAa,CAAC;AAEjF,iBAAiB;AACjB,OAAO,EACL,4BAA4B,EAC5B,2BAA2B,EAC3B,2BAA2B,GAE5B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,0BAA0B,GAE3B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EACL,6BAA6B,GAE9B,MAAM,4CAA4C,CAAC;AACpD,OAAO,EACL,6BAA6B,EAC7B,qBAAqB,EACrB,mBAAmB,GAEpB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAUxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,mBAAmB;AACnB,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGlD,qCAAqC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,2CAA2C;AAC3C,OAAO,EACL,iBAAiB,EACjB,6BAA6B,GAE9B,MAAM,gCAAgC,CAAC;AAiBxC,OAAO,EACL,UAAU,EACV,UAAU,EACV,cAAc,EACd,aAAa,EACb,WAAW,EACX,eAAe,GAChB,MAAM,qBAAqB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"gif.d.ts","sourceRoot":"","sources":["../../src/media/gif.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB,uFAAuF;IACvF,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;CACnB;AA6DD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,UAAU,GAAG,mBAAmB,GAAG,IAAI,CAwFtF"}
1
+ {"version":3,"file":"gif.d.ts","sourceRoot":"","sources":["../../src/media/gif.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB,uFAAuF;IACvF,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAqED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,UAAU,GAAG,mBAAmB,GAAG,IAAI,CAuFtF"}
package/dist/media/gif.js CHANGED
@@ -1,3 +1,10 @@
1
+ const BROWSER_MIN_DELAY_CENTISECONDS = 10;
2
+ function normalizeDelayCentiseconds(delay) {
3
+ // Chrome clamps GIF frame delays <= 1cs to 10cs (100ms); mirror browser playback timing.
4
+ if (delay <= 1)
5
+ return BROWSER_MIN_DELAY_CENTISECONDS;
6
+ return Math.max(0, delay);
7
+ }
1
8
  function readAscii(bytes, start, length) {
2
9
  if (start + length > bytes.length)
3
10
  return "";
@@ -88,7 +95,7 @@ export function parseAnimatedGifMetadata(bytes) {
88
95
  const delay = readU16LE(bytes, pos + 2);
89
96
  if (delay == null)
90
97
  return null;
91
- delaysCentiseconds.push(delay);
98
+ delaysCentiseconds.push(normalizeDelayCentiseconds(delay));
92
99
  pos += 1 + blockSize;
93
100
  if (bytes[pos] !== 0)
94
101
  return null;
@@ -130,7 +137,7 @@ export function parseAnimatedGifMetadata(bytes) {
130
137
  }
131
138
  return null;
132
139
  }
133
- const durationSeconds = delaysCentiseconds.reduce((total, delay) => total + Math.max(0, delay), 0) / 100;
140
+ const durationSeconds = delaysCentiseconds.reduce((total, delay) => total + delay, 0) / 100;
134
141
  return {
135
142
  width,
136
143
  height,
@@ -1 +1 @@
1
- {"version":3,"file":"gif.js","sourceRoot":"","sources":["../../src/media/gif.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gif.js","sourceRoot":"","sources":["../../src/media/gif.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Reject paths that escape the `base` directory — including via symlinks.
3
+ *
4
+ * `path.resolve()` collapses `.`/`..` but does NOT dereference symlinks, so a
5
+ * plain prefix check (`resolved.startsWith(base + sep)`) can be defeated by a
6
+ * symlink that lives *inside* `base` but points outside it (e.g.
7
+ * `base/link -> /etc`). A downstream `readFileSync`/`writeFileSync`/`statSync`
8
+ * then follows that link to a file outside `base`. To close this we canonicalize
9
+ * both sides with `realpathSync` before comparing.
10
+ *
11
+ * The target may not exist yet (e.g. creating a new file), so we canonicalize the
12
+ * deepest *existing* ancestor and re-attach the trailing not-yet-existing
13
+ * segments. Segments that don't exist cannot be symlinks at check time, so they
14
+ * can't redirect the path outside `base` right now. (A symlink swapped in between
15
+ * this check and the subsequent fs call is an inherent TOCTOU race this helper
16
+ * does not, and cannot by itself, defend against.)
17
+ *
18
+ * Lives at the package root rather than under `studio-api/` because callers span
19
+ * layers — `studio-api` routes, the `compiler`, the CLI, and the engine — and
20
+ * `compiler` sits below `studio-api` in the dependency graph, so it cannot import
21
+ * from there without a backwards edge.
22
+ */
23
+ export declare function isSafePath(base: string, resolved: string): boolean;
24
+ //# sourceMappingURL=safePath.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safePath.d.ts","sourceRoot":"","sources":["../src/safePath.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAgClE"}
@@ -0,0 +1,58 @@
1
+ import { resolve, sep, join, dirname, basename } from "node:path";
2
+ import { realpathSync } from "node:fs";
3
+ /**
4
+ * Reject paths that escape the `base` directory — including via symlinks.
5
+ *
6
+ * `path.resolve()` collapses `.`/`..` but does NOT dereference symlinks, so a
7
+ * plain prefix check (`resolved.startsWith(base + sep)`) can be defeated by a
8
+ * symlink that lives *inside* `base` but points outside it (e.g.
9
+ * `base/link -> /etc`). A downstream `readFileSync`/`writeFileSync`/`statSync`
10
+ * then follows that link to a file outside `base`. To close this we canonicalize
11
+ * both sides with `realpathSync` before comparing.
12
+ *
13
+ * The target may not exist yet (e.g. creating a new file), so we canonicalize the
14
+ * deepest *existing* ancestor and re-attach the trailing not-yet-existing
15
+ * segments. Segments that don't exist cannot be symlinks at check time, so they
16
+ * can't redirect the path outside `base` right now. (A symlink swapped in between
17
+ * this check and the subsequent fs call is an inherent TOCTOU race this helper
18
+ * does not, and cannot by itself, defend against.)
19
+ *
20
+ * Lives at the package root rather than under `studio-api/` because callers span
21
+ * layers — `studio-api` routes, the `compiler`, the CLI, and the engine — and
22
+ * `compiler` sits below `studio-api` in the dependency graph, so it cannot import
23
+ * from there without a backwards edge.
24
+ */
25
+ export function isSafePath(base, resolved) {
26
+ let baseReal;
27
+ try {
28
+ baseReal = realpathSync(resolve(base));
29
+ }
30
+ catch {
31
+ // Base must exist and be resolvable; fail closed if not.
32
+ return false;
33
+ }
34
+ const target = resolve(resolved);
35
+ const trailing = [];
36
+ let probe = target;
37
+ for (;;) {
38
+ let ancestorReal;
39
+ try {
40
+ ancestorReal = realpathSync(probe);
41
+ }
42
+ catch {
43
+ const parent = dirname(probe);
44
+ if (parent === probe)
45
+ return false; // walked past the filesystem root
46
+ trailing.push(basename(probe));
47
+ probe = parent;
48
+ continue;
49
+ }
50
+ // Copy before reverse(): the array is only consumed once today, but a future
51
+ // edit that loops would otherwise silently misorder the rebuilt segments.
52
+ const targetReal = trailing.length
53
+ ? join(ancestorReal, ...[...trailing].reverse())
54
+ : ancestorReal;
55
+ return targetReal === baseReal || targetReal.startsWith(baseReal + sep);
56
+ }
57
+ }
58
+ //# sourceMappingURL=safePath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safePath.js","sourceRoot":"","sources":["../src/safePath.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,QAAgB;IACvD,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACH,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,yDAAyD;QACzD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,KAAK,GAAG,MAAM,CAAC;IAEnB,SAAS,CAAC;QACR,IAAI,YAAoB,CAAC;QACzB,IAAI,CAAC;YACH,YAAY,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;YAC9B,IAAI,MAAM,KAAK,KAAK;gBAAE,OAAO,KAAK,CAAC,CAAC,kCAAkC;YACtE,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;YAC/B,KAAK,GAAG,MAAM,CAAC;YACf,SAAS;QACX,CAAC;QAED,6EAA6E;QAC7E,0EAA0E;QAC1E,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM;YAChC,CAAC,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAC;YAChD,CAAC,CAAC,YAAY,CAAC;QACjB,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,CAAC,UAAU,CAAC,QAAQ,GAAG,GAAG,CAAC,CAAC;IAC1E,CAAC;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ export interface BackupJournalResult {
2
+ backupPath: string | null;
3
+ error?: string;
4
+ }
5
+ export declare function backupPathForResponse(projectDir: string, backupPath: string | null): string | null;
6
+ export declare function snapshotBeforeWrite(projectDir: string, absPath: string, options?: {
7
+ keepPerFile?: number;
8
+ }): BackupJournalResult;
9
+ //# sourceMappingURL=backupJournal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backupJournal.d.ts","sourceRoot":"","sources":["../../../src/studio-api/helpers/backupJournal.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAUD,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,MAAM,GAAG,IAAI,CAKf;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GACrC,mBAAmB,CA0BrB"}
@@ -0,0 +1,81 @@
1
+ import { mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { Buffer } from "node:buffer";
3
+ import { join, relative } from "node:path";
4
+ import { isSafePath } from "./safePath.js";
5
+ const DEFAULT_KEEP_PER_FILE = 10;
6
+ function backupKeyForPath(path) {
7
+ return Buffer.from(path, "utf-8").toString("base64url");
8
+ }
9
+ function timestampPrefix() {
10
+ return new Date().toISOString().replace(/[:.]/g, "-");
11
+ }
12
+ export function backupPathForResponse(projectDir, backupPath) {
13
+ if (!backupPath)
14
+ return null;
15
+ const rel = relative(projectDir, backupPath);
16
+ if (!rel || rel.startsWith(".."))
17
+ return null;
18
+ return rel.split("\\").join("/");
19
+ }
20
+ export function snapshotBeforeWrite(projectDir, absPath, options = {}) {
21
+ if (!isSafePath(projectDir, absPath))
22
+ return { backupPath: null };
23
+ try {
24
+ const content = readFileSync(absPath);
25
+ const relativePath = relative(projectDir, absPath);
26
+ const backupDir = join(projectDir, ".hyperframes", "backup");
27
+ mkdirSync(backupDir, { recursive: true });
28
+ const backupKey = backupKeyForPath(relativePath);
29
+ const backupPath = nextBackupPath(backupDir, backupKey);
30
+ writeFileSync(backupPath, content);
31
+ pruneBackups(backupDir, backupKey, options.keepPerFile ?? DEFAULT_KEEP_PER_FILE);
32
+ return { backupPath };
33
+ }
34
+ catch (error) {
35
+ if (error &&
36
+ typeof error === "object" &&
37
+ "code" in error &&
38
+ (error.code === "ENOENT" || error.code === "EISDIR")) {
39
+ return { backupPath: null };
40
+ }
41
+ return { backupPath: null, error: error instanceof Error ? error.message : String(error) };
42
+ }
43
+ }
44
+ function nextBackupPath(backupDir, backupKey) {
45
+ const base = `${timestampPrefix()}-${backupKey}`;
46
+ let candidate = join(backupDir, base);
47
+ let counter = 2;
48
+ while (true) {
49
+ try {
50
+ readFileSync(candidate);
51
+ }
52
+ catch (error) {
53
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
54
+ return candidate;
55
+ }
56
+ throw error;
57
+ }
58
+ candidate = join(backupDir, `${base}-${counter}`);
59
+ counter += 1;
60
+ }
61
+ }
62
+ function pruneBackups(backupDir, backupKey, keepPerFile) {
63
+ const keep = Math.max(1, Math.floor(keepPerFile));
64
+ const suffix = `-${backupKey}`;
65
+ const numberedSuffix = new RegExp(`-${backupKey}-\\d+$`);
66
+ const matches = readdirSync(backupDir)
67
+ .filter((name) => name.endsWith(suffix) || numberedSuffix.test(name))
68
+ .map((name) => join(backupDir, name))
69
+ .sort((a, b) => {
70
+ return b.localeCompare(a);
71
+ });
72
+ for (const file of matches.slice(keep)) {
73
+ try {
74
+ unlinkSync(file);
75
+ }
76
+ catch {
77
+ // Backup pruning is best-effort and must not block the user's write.
78
+ }
79
+ }
80
+ }
81
+ //# sourceMappingURL=backupJournal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backupJournal.js","sourceRoot":"","sources":["../../../src/studio-api/helpers/backupJournal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface UnsafeMutationValue {
2
+ path: string;
3
+ reason: "non-finite-number" | "null";
4
+ }
5
+ interface FindUnsafeMutationValuesOptions {
6
+ allowNullPath?: (path: string) => boolean;
7
+ }
8
+ export declare function findUnsafeMutationValues(value: unknown, path?: string, options?: FindUnsafeMutationValuesOptions): UnsafeMutationValue[];
9
+ export declare function findUnsafeDomPatchValues(value: unknown): UnsafeMutationValue[];
10
+ export {};
11
+ //# sourceMappingURL=finiteMutation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finiteMutation.d.ts","sourceRoot":"","sources":["../../../src/studio-api/helpers/finiteMutation.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAAC;CACtC;AAED,UAAU,+BAA+B;IACvC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CAC3C;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,IAAI,SAAS,EACb,OAAO,GAAE,+BAAoC,GAC5C,mBAAmB,EAAE,CAgBvB;AAID,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,EAAE,CAI9E"}
@@ -0,0 +1,21 @@
1
+ export function findUnsafeMutationValues(value, path = "body", options = {}) {
2
+ if (value === null) {
3
+ return options.allowNullPath?.(path) ? [] : [{ path, reason: "null" }];
4
+ }
5
+ if (typeof value === "number") {
6
+ return Number.isFinite(value) ? [] : [{ path, reason: "non-finite-number" }];
7
+ }
8
+ if (!value || typeof value !== "object")
9
+ return [];
10
+ if (Array.isArray(value)) {
11
+ return value.flatMap((item, index) => findUnsafeMutationValues(item, `${path}[${index}]`, options));
12
+ }
13
+ return Object.entries(value).flatMap(([key, item]) => findUnsafeMutationValues(item, `${path}.${key}`, options));
14
+ }
15
+ const DOM_PATCH_NULL_VALUE_PATH = /^body\.operations\[\d+\]\.value$/;
16
+ export function findUnsafeDomPatchValues(value) {
17
+ return findUnsafeMutationValues(value, "body", {
18
+ allowNullPath: (path) => DOM_PATCH_NULL_VALUE_PATH.test(path),
19
+ });
20
+ }
21
+ //# sourceMappingURL=finiteMutation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finiteMutation.js","sourceRoot":"","sources":["../../../src/studio-api/helpers/finiteMutation.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,wBAAwB,CACtC,KAAc,EACd,IAAI,GAAG,MAAM,EACb,UAA2C,EAAE;IAE7C,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO,OAAO,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACnD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CACnC,wBAAwB,CAAC,IAAI,EAAE,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,OAAO,CAAC,CAC7D,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,CACnD,wBAAwB,CAAC,IAAI,EAAE,GAAG,IAAI,IAAI,GAAG,EAAE,EAAE,OAAO,CAAC,CAC1D,CAAC;AACJ,CAAC;AAED,MAAM,yBAAyB,GAAG,kCAAkC,CAAC;AAErE,MAAM,UAAU,wBAAwB,CAAC,KAAc;IACrD,OAAO,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE;QAC7C,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC;KAC9D,CAAC,CAAC;AACL,CAAC"}
@@ -1,5 +1,12 @@
1
- /** Reject paths that escape the project directory. */
2
- export declare function isSafePath(base: string, resolved: string): boolean;
1
+ export { isSafePath } from "../../safePath.js";
2
+ /**
3
+ * True when any directory segment of a relative path is a dot-directory or
4
+ * node_modules. Projects that vendor tooling assets under dot-directories
5
+ * (.hyperframes/, .cache/, …) ship example/preset HTML that must not surface
6
+ * as project compositions or studio lint targets (#1384). The file tree is
7
+ * deliberately not filtered — this only gates discovery.
8
+ */
9
+ export declare function isInHiddenOrVendorDir(relPath: string): boolean;
3
10
  /** Recursively walk a directory and return relative file paths. */
4
11
  export declare function walkDir(dir: string, prefix?: string): string[];
5
12
  //# sourceMappingURL=safePath.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"safePath.d.ts","sourceRoot":"","sources":["../../../src/studio-api/helpers/safePath.ts"],"names":[],"mappings":"AAGA,sDAAsD;AACtD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGlE;AAID,mEAAmE;AACnE,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,MAAM,EAAE,CAY1D"}
1
+ {"version":3,"file":"safePath.d.ts","sourceRoot":"","sources":["../../../src/studio-api/helpers/safePath.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAQ/C;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAG9D;AAED,mEAAmE;AACnE,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,MAAM,EAAE,CAY1D"}
@@ -1,18 +1,31 @@
1
- import { resolve, sep, join } from "node:path";
1
+ import { join } from "node:path";
2
2
  import { readdirSync } from "node:fs";
3
- /** Reject paths that escape the project directory. */
4
- export function isSafePath(base, resolved) {
5
- const norm = resolve(base) + sep;
6
- return resolved.startsWith(norm) || resolved === resolve(base);
7
- }
3
+ // `isSafePath` lives at the package root so non-studio-api layers (compiler,
4
+ // CLI, engine) can share it without a backwards dependency on studio-api.
5
+ // Re-exported here for back-compat with existing `../helpers/safePath.js` imports.
6
+ export { isSafePath } from "../../safePath.js";
8
7
  const IGNORE_DIRS = new Set([".thumbnails", "node_modules", ".git"]);
8
+ function shouldIgnoreDir(rel) {
9
+ return rel === ".hyperframes/backup";
10
+ }
11
+ /**
12
+ * True when any directory segment of a relative path is a dot-directory or
13
+ * node_modules. Projects that vendor tooling assets under dot-directories
14
+ * (.hyperframes/, .cache/, …) ship example/preset HTML that must not surface
15
+ * as project compositions or studio lint targets (#1384). The file tree is
16
+ * deliberately not filtered — this only gates discovery.
17
+ */
18
+ export function isInHiddenOrVendorDir(relPath) {
19
+ const segments = relPath.split("/");
20
+ return segments.slice(0, -1).some((seg) => seg.startsWith(".") || seg === "node_modules");
21
+ }
9
22
  /** Recursively walk a directory and return relative file paths. */
10
23
  export function walkDir(dir, prefix = "") {
11
24
  const files = [];
12
25
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
13
- if (IGNORE_DIRS.has(entry.name))
14
- continue;
15
26
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
27
+ if (IGNORE_DIRS.has(entry.name) || shouldIgnoreDir(rel))
28
+ continue;
16
29
  if (entry.isDirectory()) {
17
30
  files.push(...walkDir(join(dir, entry.name), rel));
18
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"safePath.js","sourceRoot":"","sources":["../../../src/studio-api/helpers/safePath.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,sDAAsD;AACtD,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,QAAgB;IACvD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC;IACjC,OAAO,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,aAAa,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;AAErE,mEAAmE;AACnE,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,MAAM,GAAG,EAAE;IAC9C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAC9D,IAAI,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,SAAS;QAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;QAC5D,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACrD,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"safePath.js","sourceRoot":"","sources":["../../../src/studio-api/helpers/safePath.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,6EAA6E;AAC7E,0EAA0E;AAC1E,mFAAmF;AACnF,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,aAAa,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;AAErE,SAAS,eAAe,CAAC,GAAW;IAClC,OAAO,GAAG,KAAK,qBAAqB,CAAC;AACvC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAe;IACnD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpC,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,cAAc,CAAC,CAAC;AAC5F,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,MAAM,GAAG,EAAE;IAC9C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAC9D,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;QAC5D,IAAI,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,GAAG,CAAC;YAAE,SAAS;QAClE,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACrD,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../../src/studio-api/routes/files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAcjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAywBpD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAuV7E"}
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../../src/studio-api/routes/files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAcjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAuyBpD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI,CA2Y7E"}
@@ -5,6 +5,8 @@ import { isAudioFile } from "../helpers/mime.js";
5
5
  import { generateWaveformCache } from "../helpers/waveform.js";
6
6
  import { validateUploadedMediaBuffer } from "../helpers/mediaValidation.js";
7
7
  import { isSafePath } from "../helpers/safePath.js";
8
+ import { backupPathForResponse, snapshotBeforeWrite } from "../helpers/backupJournal.js";
9
+ import { findUnsafeDomPatchValues, findUnsafeMutationValues, } from "../helpers/finiteMutation.js";
8
10
  import { removeElementFromHtml, patchElementInHtml, probeElementInSource, splitElementInHtml, } from "../helpers/sourceMutation.js";
9
11
  import { parseHTML } from "linkedom";
10
12
  /** Resolve project + safe absolute path for any project-scoped route. */
@@ -34,12 +36,28 @@ function resolveFileMutationContext(c, adapter, operation) {
34
36
  return resolveProjectPath(c, adapter, (id) => `/projects/${id}/file-mutations/${operation}/`);
35
37
  }
36
38
  /** Write `next` to `absPath` only if it differs from `original`, returning a standardized change response. */
37
- function writeIfChanged(c, absPath, original, next) {
39
+ function writeIfChanged(c, projectDir, filePath, absPath, original, next) {
38
40
  if (next === original) {
39
- return c.json({ ok: true, changed: false, content: original });
41
+ return c.json({ ok: true, changed: false, content: original, path: filePath });
40
42
  }
43
+ const backup = snapshotBeforeWrite(projectDir, absPath);
44
+ if (backup.error)
45
+ console.warn(`Failed to create backup for ${filePath}: ${backup.error}`);
41
46
  writeFileSync(absPath, next, "utf-8");
42
- return c.json({ ok: true, changed: true, content: next });
47
+ return c.json({
48
+ ok: true,
49
+ changed: true,
50
+ content: next,
51
+ path: filePath,
52
+ backupPath: backupPathForResponse(projectDir, backup.backupPath),
53
+ });
54
+ }
55
+ function rejectUnsafeMutationValues(c, unsafeFields) {
56
+ return c.json({
57
+ error: "mutation contains unsafe values",
58
+ fields: unsafeFields.map((field) => field.path),
59
+ unsafeValues: unsafeFields,
60
+ }, 400);
43
61
  }
44
62
  /**
45
63
  * Parse the request body and validate that `target` is present.
@@ -483,8 +501,15 @@ export function registerFileRoutes(api, adapter) {
483
501
  return res.error;
484
502
  ensureDir(res.absPath);
485
503
  const body = await c.req.text();
504
+ const backup = snapshotBeforeWrite(res.project.dir, res.absPath);
505
+ if (backup.error)
506
+ console.warn(`Failed to create backup for ${res.filePath}: ${backup.error}`);
486
507
  writeFileSync(res.absPath, body, "utf-8");
487
- return c.json({ ok: true });
508
+ return c.json({
509
+ ok: true,
510
+ path: res.filePath,
511
+ backupPath: backupPathForResponse(res.project.dir, backup.backupPath),
512
+ });
488
513
  });
489
514
  // ── Create (fail if exists) ──
490
515
  api.post("/projects/:id/files/*", async (c) => {
@@ -505,13 +530,19 @@ export function registerFileRoutes(api, adapter) {
505
530
  if ("error" in res)
506
531
  return res.error;
507
532
  const stat = statSync(res.absPath);
533
+ const backup = snapshotBeforeWrite(res.project.dir, res.absPath);
534
+ if (backup.error)
535
+ console.warn(`Failed to create backup for ${res.filePath}: ${backup.error}`);
508
536
  if (stat.isDirectory()) {
509
537
  rmSync(res.absPath, { recursive: true });
510
538
  }
511
539
  else {
512
540
  unlinkSync(res.absPath);
513
541
  }
514
- return c.json({ ok: true });
542
+ return c.json({
543
+ ok: true,
544
+ backupPath: backupPathForResponse(res.project.dir, backup.backupPath),
545
+ });
515
546
  });
516
547
  api.post("/projects/:id/file-mutations/remove-element/*", async (c) => {
517
548
  const ctx = await resolveFileMutationContext(c, adapter, "remove-element");
@@ -524,7 +555,7 @@ export function registerFileRoutes(api, adapter) {
524
555
  if ("error" in parsed)
525
556
  return parsed.error;
526
557
  const originalContent = readFileSync(ctx.absPath, "utf-8");
527
- return writeIfChanged(c, ctx.absPath, originalContent, removeElementFromHtml(originalContent, parsed.target));
558
+ return writeIfChanged(c, ctx.project.dir, ctx.filePath, ctx.absPath, originalContent, removeElementFromHtml(originalContent, parsed.target));
528
559
  });
529
560
  api.post("/projects/:id/file-mutations/split-element/*", async (c) => {
530
561
  const ctx = await resolveFileMutationContext(c, adapter, "split-element");
@@ -545,10 +576,20 @@ export function registerFileRoutes(api, adapter) {
545
576
  }
546
577
  const result = splitElementInHtml(originalContent, parsed.target, parsed.body.splitTime, parsed.body.newId);
547
578
  if (!result.matched) {
548
- return c.json({ ok: false, changed: false, content: originalContent });
579
+ return c.json({ ok: false, changed: false, content: originalContent, path: ctx.filePath });
549
580
  }
581
+ const backup = snapshotBeforeWrite(ctx.project.dir, ctx.absPath);
582
+ if (backup.error)
583
+ console.warn(`Failed to create backup for ${ctx.filePath}: ${backup.error}`);
550
584
  writeFileSync(ctx.absPath, result.html, "utf-8");
551
- return c.json({ ok: true, changed: true, content: result.html, newId: result.newId });
585
+ return c.json({
586
+ ok: true,
587
+ changed: true,
588
+ content: result.html,
589
+ newId: result.newId,
590
+ path: ctx.filePath,
591
+ backupPath: backupPathForResponse(ctx.project.dir, backup.backupPath),
592
+ });
552
593
  });
553
594
  api.post("/projects/:id/file-mutations/patch-element/*", async (c) => {
554
595
  const ctx = await resolveFileMutationContext(c, adapter, "patch-element");
@@ -560,6 +601,10 @@ export function registerFileRoutes(api, adapter) {
560
601
  if (!Array.isArray(parsed.body.operations) || parsed.body.operations.length === 0) {
561
602
  return c.json({ error: "target and operations required" }, 400);
562
603
  }
604
+ const unsafeFields = findUnsafeDomPatchValues(parsed.body);
605
+ if (unsafeFields.length > 0) {
606
+ return rejectUnsafeMutationValues(c, unsafeFields);
607
+ }
563
608
  let originalContent;
564
609
  try {
565
610
  originalContent = readFileSync(ctx.absPath, "utf-8");
@@ -569,10 +614,26 @@ export function registerFileRoutes(api, adapter) {
569
614
  }
570
615
  const { html: patched, matched } = patchElementInHtml(originalContent, parsed.target, parsed.body.operations);
571
616
  if (patched === originalContent) {
572
- return c.json({ ok: true, changed: false, matched, content: originalContent });
617
+ return c.json({
618
+ ok: true,
619
+ changed: false,
620
+ matched,
621
+ content: originalContent,
622
+ path: ctx.filePath,
623
+ });
573
624
  }
625
+ const backup = snapshotBeforeWrite(ctx.project.dir, ctx.absPath);
626
+ if (backup.error)
627
+ console.warn(`Failed to create backup for ${ctx.filePath}: ${backup.error}`);
574
628
  writeFileSync(ctx.absPath, patched, "utf-8");
575
- return c.json({ ok: true, changed: true, matched, content: patched });
629
+ return c.json({
630
+ ok: true,
631
+ changed: true,
632
+ matched,
633
+ content: patched,
634
+ path: ctx.filePath,
635
+ backupPath: backupPathForResponse(ctx.project.dir, backup.backupPath),
636
+ });
576
637
  });
577
638
  api.post("/projects/:id/file-mutations/probe-element/*", async (c) => {
578
639
  const ctx = await resolveFileMutationContext(c, adapter, "probe-element");
@@ -687,6 +748,10 @@ export function registerFileRoutes(api, adapter) {
687
748
  if (!body || !body.type) {
688
749
  return c.json({ error: "mutation type required" }, 400);
689
750
  }
751
+ const unsafeFields = findUnsafeMutationValues(body);
752
+ if (unsafeFields.length > 0) {
753
+ return rejectUnsafeMutationValues(c, unsafeFields);
754
+ }
690
755
  let html = readFileSync(res.absPath, "utf-8");
691
756
  let block = extractGsapScriptBlock(html);
692
757
  if (!block && (body.type === "add" || body.type === "add-with-keyframes")) {
@@ -721,7 +786,12 @@ export function registerFileRoutes(api, adapter) {
721
786
  const newScript = typeof result === "string" ? result : result.script;
722
787
  const changed = newScript !== block.scriptText;
723
788
  const newHtml = changed ? block.replaceScript(newScript) : html;
789
+ let backupPath = null;
724
790
  if (changed) {
791
+ const backup = snapshotBeforeWrite(res.project.dir, res.absPath);
792
+ if (backup.error)
793
+ console.warn(`Failed to create backup for ${res.filePath}: ${backup.error}`);
794
+ backupPath = backupPathForResponse(res.project.dir, backup.backupPath);
725
795
  writeFileSync(res.absPath, newHtml, "utf-8");
726
796
  }
727
797
  const { parseGsapScript } = await loadGsapParser();
@@ -733,6 +803,8 @@ export function registerFileRoutes(api, adapter) {
733
803
  before: html,
734
804
  after: newHtml,
735
805
  scriptText: newScript,
806
+ path: res.filePath,
807
+ backupPath,
736
808
  };
737
809
  if (typeof result !== "string" && result.skippedSelectors.length > 0) {
738
810
  responsePayload.skippedSelectors = result.skippedSelectors;