@hyperframes/studio-server 0.8.56 → 0.8.58

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
@@ -297,8 +297,8 @@ import {
297
297
  existsSync as existsSync3,
298
298
  ftruncateSync,
299
299
  openSync,
300
- readFileSync as readFileSync4,
301
- writeFileSync as writeFileSync4,
300
+ readFileSync as readFileSync5,
301
+ writeFileSync as writeFileSync5,
302
302
  writeSync,
303
303
  mkdirSync as mkdirSync3,
304
304
  unlinkSync as unlinkSync2,
@@ -607,6 +607,13 @@ function recordFileWriteReceipt(absPath, receipt) {
607
607
  current.push({ ...receipt, recordedAt: now });
608
608
  receipts.set(absPath, current);
609
609
  }
610
+ function clearFileWriteReceipt(absPath, version, writeToken) {
611
+ const current = (receipts.get(absPath) ?? []).filter(
612
+ (entry) => entry.version !== version || entry.writeToken !== writeToken
613
+ );
614
+ if (current.length > 0) receipts.set(absPath, current);
615
+ else receipts.delete(absPath);
616
+ }
610
617
  function identifyFileWrite(absPath, expectedVersion) {
611
618
  const now = Date.now();
612
619
  const current = (receipts.get(absPath) ?? []).filter(
@@ -624,6 +631,79 @@ function identifyFileWrite(absPath, expectedVersion) {
624
631
  }
625
632
  var consumeFileWriteReceipt = identifyFileWrite;
626
633
 
634
+ // src/helpers/applyFileMutations.ts
635
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
636
+ function applyFileMutations(projectDir, mutations, requestToken, writeFile = writeFileSync4) {
637
+ const prepared = mutations.map((mutation) => ({
638
+ ...mutation,
639
+ before: mutation.before ?? readFileSync3(mutation.absPath, "utf-8")
640
+ }));
641
+ const results = [];
642
+ const attempted = [];
643
+ try {
644
+ for (const mutation of prepared) {
645
+ results.push(applyOneMutation(projectDir, mutation, requestToken, writeFile, attempted));
646
+ }
647
+ return results;
648
+ } catch (error) {
649
+ const rollbackErrors = [];
650
+ for (const mutation of attempted.reverse()) {
651
+ try {
652
+ writeFile(mutation.absPath, mutation.before, "utf-8");
653
+ clearFileWriteReceipt(mutation.absPath, mutation.version, mutation.writeToken);
654
+ } catch (rollbackError) {
655
+ rollbackErrors.push(rollbackError);
656
+ }
657
+ }
658
+ if (rollbackErrors.length > 0) {
659
+ throw new AggregateError(
660
+ [error, ...rollbackErrors],
661
+ "File mutation failed and rollback did not complete"
662
+ );
663
+ }
664
+ throw error;
665
+ }
666
+ }
667
+ function applyOneMutation(projectDir, mutation, requestToken, writeFile, attempted) {
668
+ const current = readFileSync3(mutation.absPath, "utf-8");
669
+ assertExpectedVersion(mutation.expectedVersion, current);
670
+ if (mutation.after === mutation.before) {
671
+ return {
672
+ ...mutation,
673
+ before: current,
674
+ changed: false,
675
+ backupPath: null,
676
+ version: fileContentVersion(mutation.before),
677
+ writeToken: null
678
+ };
679
+ }
680
+ const backup = snapshotBeforeWrite(projectDir, mutation.absPath);
681
+ if (backup.error) throw new Error(`backup failed: ${backup.error}`);
682
+ const before = current;
683
+ const version = fileContentVersion(mutation.after);
684
+ const writeToken = createWriteToken(requestToken);
685
+ attempted.push({ ...mutation, before, version, writeToken });
686
+ writeFile(mutation.absPath, mutation.after, "utf-8");
687
+ recordFileWriteReceipt(mutation.absPath, {
688
+ path: mutation.sourceFile,
689
+ version,
690
+ writeToken
691
+ });
692
+ return {
693
+ ...mutation,
694
+ before,
695
+ changed: true,
696
+ backupPath: backupPathForResponse(projectDir, backup.backupPath),
697
+ version,
698
+ writeToken
699
+ };
700
+ }
701
+ function assertExpectedVersion(expectedVersion, current) {
702
+ if (expectedVersion !== void 0 && fileContentVersion(current) !== expectedVersion) {
703
+ throw new Error("file changed since the timeline was read");
704
+ }
705
+ }
706
+
627
707
  // src/routes/files.ts
628
708
  import { classifyPropertyGroup } from "@hyperframes/parsers/gsap-constants";
629
709
  import { parseGsapScriptAcorn } from "@hyperframes/parsers/gsap-parser-acorn";
@@ -659,7 +739,7 @@ import {
659
739
  import { parseHTML as parseHTML2 } from "linkedom";
660
740
 
661
741
  // src/helpers/compositionInsertion.ts
662
- import { existsSync as existsSync2, readFileSync as readFileSync3, realpathSync } from "fs";
742
+ import { existsSync as existsSync2, readFileSync as readFileSync4, realpathSync } from "fs";
663
743
  import { randomUUID as randomUUID2 } from "crypto";
664
744
  import { dirname as dirname2, relative as relative3, resolve as resolve2, sep as sep2 } from "path";
665
745
  import { parseHTML } from "linkedom";
@@ -731,7 +811,7 @@ function validateDependencyGraph(projectDir, targetAbs, sourceAbs) {
731
811
  }
732
812
  if (visited.has(file)) return;
733
813
  visiting.add(file);
734
- const source = readFileSync3(file, "utf-8");
814
+ const source = readFileSync4(file, "utf-8");
735
815
  const { document } = compositionRoot(source);
736
816
  for (const host of descendants(document, "[data-composition-src]")) {
737
817
  const dependency = host.getAttribute("data-composition-src");
@@ -795,7 +875,7 @@ function insertCompositionIntoSource(input) {
795
875
  const targetAbs = canonicalProjectFile(input.projectDir, input.targetPath);
796
876
  const sourceAbs = canonicalProjectFile(input.projectDir, input.sourcePath);
797
877
  validateDependencyGraph(input.projectDir, targetAbs, sourceAbs);
798
- const source = readFileSync3(sourceAbs, "utf-8");
878
+ const source = readFileSync4(sourceAbs, "utf-8");
799
879
  const sourceComposition = compositionRoot(source).root;
800
880
  const duration = positiveAttribute(
801
881
  sourceComposition,
@@ -983,7 +1063,7 @@ function foldElementPatches(originalContent, patches) {
983
1063
  }
984
1064
  return { content, matched };
985
1065
  }
986
- function commitElementPatchBatches(projectDir, batches, writeFile = writeFileSync4) {
1066
+ function commitElementPatchBatches(projectDir, batches, writeFile = writeFileSync5, requestToken) {
987
1067
  const resolvedPaths = /* @__PURE__ */ new Set();
988
1068
  const prepared = [];
989
1069
  for (const batch of batches) {
@@ -993,7 +1073,7 @@ function commitElementPatchBatches(projectDir, batches, writeFile = writeFileSyn
993
1073
  resolvedPaths.add(absPath);
994
1074
  let before;
995
1075
  try {
996
- before = readFileSync4(absPath, "utf-8");
1076
+ before = readFileSync5(absPath, "utf-8");
997
1077
  } catch {
998
1078
  return { error: "not-found", sourceFile: batch.sourceFile };
999
1079
  }
@@ -1019,64 +1099,34 @@ function commitElementPatchBatches(projectDir, batches, writeFile = writeFileSyn
1019
1099
  }))
1020
1100
  };
1021
1101
  }
1022
- const files = [];
1023
- const attemptedWrites = [];
1024
- try {
1025
- for (const file of prepared) {
1026
- if (file.after === file.before) {
1027
- files.push({
1028
- sourceFile: file.sourceFile,
1029
- changed: false,
1030
- matched: file.matched,
1031
- before: file.before,
1032
- after: file.before
1033
- });
1034
- continue;
1035
- }
1036
- const backup = snapshotBeforeWrite(projectDir, file.absPath);
1037
- if (backup.error) {
1038
- throw new Error(`Failed to create backup for ${file.sourceFile}: ${backup.error}`);
1039
- }
1040
- attemptedWrites.push(file);
1041
- writeFile(file.absPath, file.after, "utf-8");
1042
- files.push({
1043
- sourceFile: file.sourceFile,
1044
- changed: true,
1045
- matched: file.matched,
1046
- before: file.before,
1047
- after: file.after,
1048
- backupPath: backupPathForResponse(projectDir, backup.backupPath)
1049
- });
1050
- }
1051
- } catch (error) {
1052
- const rollbackErrors = [];
1053
- for (const file of attemptedWrites.reverse()) {
1054
- try {
1055
- writeFile(file.absPath, file.before, "utf-8");
1056
- } catch (rollbackError) {
1057
- rollbackErrors.push(rollbackError);
1058
- }
1059
- }
1060
- if (rollbackErrors.length > 0) {
1061
- throw new AggregateError(
1062
- [error, ...rollbackErrors],
1063
- "Element patch batch failed and rollback did not complete"
1064
- );
1065
- }
1066
- throw error;
1067
- }
1102
+ const applied = applyFileMutations(
1103
+ projectDir,
1104
+ prepared.map(({ sourceFile, absPath, before, after }) => ({
1105
+ sourceFile,
1106
+ absPath,
1107
+ before,
1108
+ after
1109
+ })),
1110
+ requestToken,
1111
+ writeFile
1112
+ );
1113
+ const files = applied.map((file, index) => ({
1114
+ sourceFile: file.sourceFile,
1115
+ changed: file.changed,
1116
+ matched: prepared[index]?.matched ?? [],
1117
+ before: file.before,
1118
+ after: file.after,
1119
+ backupPath: file.backupPath ?? void 0
1120
+ }));
1068
1121
  return { durable: true, files };
1069
1122
  }
1070
1123
  function commitElementPatchBatchesWithReceipts(c, projectDir, batches) {
1071
- const result = commitElementPatchBatches(projectDir, batches);
1072
- if ("error" in result || !result.durable) return result;
1073
- for (const file of result.files) {
1074
- if (!file.changed) continue;
1075
- const absPath = resolveWithinProject(projectDir, file.sourceFile);
1076
- if (!absPath) throw new Error(`Committed element patch escaped project: ${file.sourceFile}`);
1077
- recordMutationReceipt(c, file.sourceFile, absPath, file.after);
1078
- }
1079
- return result;
1124
+ return commitElementPatchBatches(
1125
+ projectDir,
1126
+ batches,
1127
+ writeFileSync5,
1128
+ c.req.header("X-Hyperframes-Write-Token")
1129
+ );
1080
1130
  }
1081
1131
  function recordMutationReceipt(c, filePath, absPath, html) {
1082
1132
  const version = fileContentVersion(html);
@@ -1085,7 +1135,7 @@ function recordMutationReceipt(c, filePath, absPath, html) {
1085
1135
  return { version, writeToken };
1086
1136
  }
1087
1137
  function writeFileWithReceipt(c, filePath, absPath, html) {
1088
- writeFileSync4(absPath, html, "utf-8");
1138
+ writeFileSync5(absPath, html, "utf-8");
1089
1139
  return recordMutationReceipt(c, filePath, absPath, html);
1090
1140
  }
1091
1141
  function writeMutationResult(c, projectDir, filePath, absPath, html) {
@@ -1170,11 +1220,11 @@ function updateReferences(projectDir, oldPath, newPath) {
1170
1220
  let updatedCount = 0;
1171
1221
  for (const file of textFiles) {
1172
1222
  if (!isSafePath(projectDir, file)) continue;
1173
- const content = readFileSync4(file, "utf-8");
1223
+ const content = readFileSync5(file, "utf-8");
1174
1224
  if (!content.includes(oldPath)) continue;
1175
1225
  const updated = content.split(oldPath).join(newPath);
1176
1226
  if (updated !== content) {
1177
- writeFileSync4(file, updated, "utf-8");
1227
+ writeFileSync5(file, updated, "utf-8");
1178
1228
  updatedCount++;
1179
1229
  }
1180
1230
  }
@@ -1444,7 +1494,7 @@ function containsRawGsapExpression(value) {
1444
1494
  return Object.values(value).some(containsRawGsapExpression);
1445
1495
  }
1446
1496
  async function prepareGsapMutationScript(c, res, firstMutation) {
1447
- const beforeHtml = readFileSync4(res.absPath, "utf-8");
1497
+ const beforeHtml = readFileSync5(res.absPath, "utf-8");
1448
1498
  let html = beforeHtml;
1449
1499
  let block = extractGsapScriptBlock(html);
1450
1500
  if (!block && (firstMutation.type === "add" || firstMutation.type === "add-with-keyframes")) {
@@ -1511,7 +1561,7 @@ async function applyGsapMutations(c, res, mutations) {
1511
1561
  const changed = block.scriptText !== initialScript;
1512
1562
  const newHtml = changed ? block.replaceScript(block.scriptText) : html;
1513
1563
  let backupPath = null;
1514
- if (readFileSync4(res.absPath, "utf-8") !== beforeHtml) {
1564
+ if (readFileSync5(res.absPath, "utf-8") !== beforeHtml) {
1515
1565
  return c.json({ error: "file changed during GSAP mutation", conflict: true }, 409);
1516
1566
  }
1517
1567
  if (changed) {
@@ -2294,7 +2344,7 @@ async function processUploadedFiles(formData, targetDir, projectDir) {
2294
2344
  try {
2295
2345
  const fd = openSync(finalPath, "wx");
2296
2346
  try {
2297
- writeFileSync4(fd, buffer);
2347
+ writeFileSync5(fd, buffer);
2298
2348
  } finally {
2299
2349
  closeSync(fd);
2300
2350
  }
@@ -2332,7 +2382,7 @@ function registerFileRoutes(api, adapter) {
2332
2382
  }
2333
2383
  return c.json({ error: "not found" }, 404);
2334
2384
  }
2335
- const content = readFileSync4(res.absPath);
2385
+ const content = readFileSync5(res.absPath);
2336
2386
  const version = fileContentVersion(content);
2337
2387
  c.header("ETag", version);
2338
2388
  return c.json({ filename: res.filePath, content: content.toString("utf-8"), version });
@@ -2346,7 +2396,7 @@ function registerFileRoutes(api, adapter) {
2346
2396
  if (expectedVersion === null && !createOnly) {
2347
2397
  let currentContent = null;
2348
2398
  try {
2349
- currentContent = readFileSync4(res.absPath);
2399
+ currentContent = readFileSync5(res.absPath);
2350
2400
  } catch (error) {
2351
2401
  if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
2352
2402
  throw error;
@@ -2372,7 +2422,7 @@ function registerFileRoutes(api, adapter) {
2372
2422
  if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
2373
2423
  throw error;
2374
2424
  }
2375
- const currentContent = readFileSync4(res.absPath);
2425
+ const currentContent = readFileSync5(res.absPath);
2376
2426
  return c.json(
2377
2427
  {
2378
2428
  error: "file conflict",
@@ -2407,7 +2457,7 @@ function registerFileRoutes(api, adapter) {
2407
2457
  );
2408
2458
  }
2409
2459
  try {
2410
- const currentContent = readFileSync4(fd);
2460
+ const currentContent = readFileSync5(fd);
2411
2461
  const currentVersion = fileContentVersion(currentContent);
2412
2462
  if (expectedVersion !== currentVersion) {
2413
2463
  return c.json(
@@ -2446,7 +2496,7 @@ function registerFileRoutes(api, adapter) {
2446
2496
  ensureDir(res.absPath);
2447
2497
  const body = Buffer.from(await c.req.arrayBuffer());
2448
2498
  try {
2449
- writeFileSync4(res.absPath, body, { flag: "wx" });
2499
+ writeFileSync5(res.absPath, body, { flag: "wx" });
2450
2500
  } catch (error) {
2451
2501
  if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
2452
2502
  throw error;
@@ -2480,7 +2530,7 @@ function registerFileRoutes(api, adapter) {
2480
2530
  }
2481
2531
  let before;
2482
2532
  try {
2483
- before = readFileSync4(ctx.absPath, "utf-8");
2533
+ before = readFileSync5(ctx.absPath, "utf-8");
2484
2534
  } catch (error) {
2485
2535
  if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
2486
2536
  throw error;
@@ -2537,7 +2587,7 @@ function registerFileRoutes(api, adapter) {
2537
2587
  }
2538
2588
  const parsed = await parseMutationBody(c);
2539
2589
  if ("error" in parsed) return parsed.error;
2540
- const originalContent = readFileSync4(ctx.absPath, "utf-8");
2590
+ const originalContent = readFileSync5(ctx.absPath, "utf-8");
2541
2591
  return writeIfChanged(
2542
2592
  c,
2543
2593
  ctx.project.dir,
@@ -2558,7 +2608,7 @@ function registerFileRoutes(api, adapter) {
2558
2608
  if (!Array.isArray(targets) || targets.length === 0) {
2559
2609
  return c.json({ error: "targets required" }, 400);
2560
2610
  }
2561
- const originalContent = readFileSync4(ctx.absPath, "utf-8");
2611
+ const originalContent = readFileSync5(ctx.absPath, "utf-8");
2562
2612
  let next = originalContent;
2563
2613
  for (const target of targets) {
2564
2614
  next = removeElementFromHtml(next, target);
@@ -2591,7 +2641,7 @@ function registerFileRoutes(api, adapter) {
2591
2641
  seen.add(absPath);
2592
2642
  let before;
2593
2643
  try {
2594
- before = readFileSync4(absPath, "utf-8");
2644
+ before = readFileSync5(absPath, "utf-8");
2595
2645
  } catch {
2596
2646
  return c.json({ error: `not found: ${file.path}` }, 404);
2597
2647
  }
@@ -2618,7 +2668,7 @@ function registerFileRoutes(api, adapter) {
2618
2668
  prepared.push(folded);
2619
2669
  }
2620
2670
  for (const file of prepared) {
2621
- const current = readFileSync4(file.absPath, "utf-8");
2671
+ const current = readFileSync5(file.absPath, "utf-8");
2622
2672
  if (current !== file.before) {
2623
2673
  return c.json(
2624
2674
  {
@@ -2648,7 +2698,7 @@ function registerFileRoutes(api, adapter) {
2648
2698
  const written = [];
2649
2699
  try {
2650
2700
  for (const file of prepared) {
2651
- writeFileSync4(file.absPath, file.after, "utf-8");
2701
+ writeFileSync5(file.absPath, file.after, "utf-8");
2652
2702
  written.push(file);
2653
2703
  recordFileWriteReceipt(file.absPath, {
2654
2704
  path: file.path,
@@ -2660,12 +2710,12 @@ function registerFileRoutes(api, adapter) {
2660
2710
  const conflicts = [];
2661
2711
  for (const file of written.reverse()) {
2662
2712
  try {
2663
- const current = readFileSync4(file.absPath, "utf-8");
2713
+ const current = readFileSync5(file.absPath, "utf-8");
2664
2714
  if (current !== file.after) {
2665
2715
  conflicts.push(file.path);
2666
2716
  continue;
2667
2717
  }
2668
- writeFileSync4(file.absPath, file.before, "utf-8");
2718
+ writeFileSync5(file.absPath, file.before, "utf-8");
2669
2719
  recordFileWriteReceipt(file.absPath, {
2670
2720
  path: file.path,
2671
2721
  version: fileContentVersion(file.before),
@@ -2708,7 +2758,7 @@ function registerFileRoutes(api, adapter) {
2708
2758
  const fallbackTiming = typeof parsed.body.elementStart === "number" && typeof parsed.body.elementDuration === "number" ? { start: parsed.body.elementStart, duration: parsed.body.elementDuration } : void 0;
2709
2759
  let originalContent;
2710
2760
  try {
2711
- originalContent = readFileSync4(ctx.absPath, "utf-8");
2761
+ originalContent = readFileSync5(ctx.absPath, "utf-8");
2712
2762
  } catch {
2713
2763
  return c.json({ error: "not found" }, 404);
2714
2764
  }
@@ -2764,7 +2814,7 @@ function registerFileRoutes(api, adapter) {
2764
2814
  }
2765
2815
  let originalContent;
2766
2816
  try {
2767
- originalContent = readFileSync4(ctx.absPath, "utf-8");
2817
+ originalContent = readFileSync5(ctx.absPath, "utf-8");
2768
2818
  } catch {
2769
2819
  return c.json({ error: "not found" }, 404);
2770
2820
  }
@@ -2865,7 +2915,7 @@ function registerFileRoutes(api, adapter) {
2865
2915
  }
2866
2916
  let originalContent;
2867
2917
  try {
2868
- originalContent = readFileSync4(ctx.absPath, "utf-8");
2918
+ originalContent = readFileSync5(ctx.absPath, "utf-8");
2869
2919
  } catch {
2870
2920
  return c.json({ error: "not found" }, 404);
2871
2921
  }
@@ -2920,7 +2970,7 @@ function registerFileRoutes(api, adapter) {
2920
2970
  ).map((entry) => ({ target: entry.target, track: entry.track }));
2921
2971
  let originalContent;
2922
2972
  try {
2923
- originalContent = readFileSync4(ctx.absPath, "utf-8");
2973
+ originalContent = readFileSync5(ctx.absPath, "utf-8");
2924
2974
  } catch {
2925
2975
  return c.json({ error: "not found" }, 404);
2926
2976
  }
@@ -2949,7 +2999,7 @@ function registerFileRoutes(api, adapter) {
2949
2999
  if ("error" in parsed) return parsed.error;
2950
3000
  let content;
2951
3001
  try {
2952
- content = readFileSync4(ctx.absPath, "utf-8");
3002
+ content = readFileSync5(ctx.absPath, "utf-8");
2953
3003
  } catch {
2954
3004
  return c.json({ exists: false });
2955
3005
  }
@@ -2992,7 +3042,7 @@ function registerFileRoutes(api, adapter) {
2992
3042
  return c.json({ error: "forbidden" }, 403);
2993
3043
  }
2994
3044
  ensureDir(destAbs);
2995
- writeFileSync4(destAbs, readFileSync4(srcAbs));
3045
+ writeFileSync5(destAbs, readFileSync5(srcAbs));
2996
3046
  return c.json({ ok: true, path: copyPath }, 201);
2997
3047
  });
2998
3048
  const MAX_UPLOAD_BYTES = 500 * 1024 * 1024;
@@ -3022,7 +3072,7 @@ function registerFileRoutes(api, adapter) {
3022
3072
  mustExist: true
3023
3073
  });
3024
3074
  if ("error" in res) return res.error;
3025
- const html = readFileSync4(res.absPath, "utf-8");
3075
+ const html = readFileSync5(res.absPath, "utf-8");
3026
3076
  const block = extractGsapScriptBlock(html);
3027
3077
  if (!block) {
3028
3078
  return c.json({
@@ -3081,17 +3131,17 @@ function registerFileRoutes(api, adapter) {
3081
3131
  if (!body || typeof body.expected !== "string" || typeof body.restore !== "string") {
3082
3132
  return c.json({ error: "expected and restore contents required" }, 400);
3083
3133
  }
3084
- const current = readFileSync4(res.absPath, "utf-8");
3134
+ const current = readFileSync5(res.absPath, "utf-8");
3085
3135
  if (current !== body.expected) {
3086
3136
  return c.json({ ok: true, restored: false, conflict: true });
3087
3137
  }
3088
- writeFileSync4(res.absPath, body.restore, "utf-8");
3138
+ writeFileSync5(res.absPath, body.restore, "utf-8");
3089
3139
  return c.json({ ok: true, restored: true, conflict: false });
3090
3140
  });
3091
3141
  }
3092
3142
 
3093
3143
  // src/routes/preview.ts
3094
- import { createReadStream, existsSync as existsSync5, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
3144
+ import { createReadStream, existsSync as existsSync5, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
3095
3145
  import { Readable } from "stream";
3096
3146
  import { join as join8, resolve as resolve4 } from "path";
3097
3147
  import { createHash as createHash3 } from "crypto";
@@ -3099,7 +3149,7 @@ import { injectScriptsIntoHtml, stripEmbeddedRuntimeScripts as stripEmbeddedRunt
3099
3149
  import { isWithinProjectRoot } from "@hyperframes/parsers/asset-resolution";
3100
3150
 
3101
3151
  // src/helpers/subComposition.ts
3102
- import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
3152
+ import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
3103
3153
  import { join as join7 } from "path";
3104
3154
  import { parseHTML as parseHTML3 } from "linkedom";
3105
3155
  import {
@@ -3211,7 +3261,7 @@ function tagRootCompositionFile(bodyHtml, compPath) {
3211
3261
  function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, rawOverride) {
3212
3262
  const compFile = join7(projectDir, compPath);
3213
3263
  if (!existsSync4(compFile)) return null;
3214
- const rawComp = rawOverride ?? readFileSync5(compFile, "utf-8");
3264
+ const rawComp = rawOverride ?? readFileSync6(compFile, "utf-8");
3215
3265
  let compHeadContent = "";
3216
3266
  let rewrittenContent;
3217
3267
  let htmlAttrs = "";
@@ -3244,7 +3294,7 @@ function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, raw
3244
3294
  const indexPath = join7(projectDir, "index.html");
3245
3295
  let headContent = "";
3246
3296
  if (existsSync4(indexPath)) {
3247
- const indexHtml = readFileSync5(indexPath, "utf-8");
3297
+ const indexHtml = readFileSync6(indexPath, "utf-8");
3248
3298
  const headMatch = indexHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
3249
3299
  headContent = headMatch?.[1] ?? "";
3250
3300
  }
@@ -3288,8 +3338,8 @@ import {
3288
3338
  fstatSync,
3289
3339
  ftruncateSync as ftruncateSync2,
3290
3340
  openSync as openSync2,
3291
- readFileSync as readFileSync6,
3292
- writeFileSync as writeFileSync5,
3341
+ readFileSync as readFileSync7,
3342
+ writeFileSync as writeFileSync6,
3293
3343
  writeSync as writeSync2
3294
3344
  } from "fs";
3295
3345
  function persistHfIdsIfNeeded(filePath, html) {
@@ -3298,9 +3348,9 @@ function persistHfIdsIfNeeded(filePath, html) {
3298
3348
  const idsAfter = (normalized.match(/\bdata-hf-id=/g) ?? []).length;
3299
3349
  if (idsAfter > idsBefore) {
3300
3350
  try {
3301
- const current = readFileSync6(filePath, "utf-8");
3351
+ const current = readFileSync7(filePath, "utf-8");
3302
3352
  if (current === html) {
3303
- writeFileSync5(filePath, normalized, "utf-8");
3353
+ writeFileSync6(filePath, normalized, "utf-8");
3304
3354
  }
3305
3355
  } catch (err) {
3306
3356
  console.warn("[hyperframes] persistHfIdsIfNeeded: failed to write ids to disk:", err);
@@ -3326,7 +3376,7 @@ function stampFileHfIds(filePath) {
3326
3376
  if (fd === null) return null;
3327
3377
  try {
3328
3378
  if (!fstatSync(fd).isFile()) return null;
3329
- const html = readFileSync6(fd, "utf-8");
3379
+ const html = readFileSync7(fd, "utf-8");
3330
3380
  const normalized = ensureHfIds(html);
3331
3381
  const idsBefore = (html.match(/\bdata-hf-id=/g) ?? []).length;
3332
3382
  const idsAfter = (normalized.match(/\bdata-hf-id=/g) ?? []).length;
@@ -3388,7 +3438,7 @@ function readStudioMotionManifestContent(projectDir) {
3388
3438
  const manifestPath = join8(projectDir, STUDIO_MOTION_PATH);
3389
3439
  if (!existsSync5(manifestPath)) return "";
3390
3440
  try {
3391
- return readFileSync7(manifestPath, "utf-8");
3441
+ return readFileSync8(manifestPath, "utf-8");
3392
3442
  } catch {
3393
3443
  return "";
3394
3444
  }
@@ -3534,14 +3584,14 @@ function resolveProjectMainHtml(projectDir, projectId) {
3534
3584
  const indexPath = join8(projectDir, "index.html");
3535
3585
  if (existsSync5(indexPath)) {
3536
3586
  return {
3537
- html: readFileSync7(indexPath, "utf-8"),
3587
+ html: readFileSync8(indexPath, "utf-8"),
3538
3588
  compositionPath: "index.html"
3539
3589
  };
3540
3590
  }
3541
3591
  const blockHtmlPath = join8(projectDir, `${projectId}.html`);
3542
3592
  if (existsSync5(blockHtmlPath)) {
3543
3593
  return {
3544
- html: readFileSync7(blockHtmlPath, "utf-8"),
3594
+ html: readFileSync8(blockHtmlPath, "utf-8"),
3545
3595
  compositionPath: `${projectId}.html`
3546
3596
  };
3547
3597
  }
@@ -3735,7 +3785,7 @@ ${runtimeTag}`;
3735
3785
  }
3736
3786
  servedContentType = PROXY_VARIANT_CONFIG[proxyVariant].contentType;
3737
3787
  }
3738
- const textBuffer = isText ? Buffer.from(readFileSync7(file, "utf-8"), "utf-8") : null;
3788
+ const textBuffer = isText ? Buffer.from(readFileSync8(file, "utf-8"), "utf-8") : null;
3739
3789
  const totalSize = textBuffer ? textBuffer.length : statSync3(servedPath).size;
3740
3790
  const bodyFor = (start, end) => textBuffer ? new Uint8Array(textBuffer.subarray(start, end + 1)) : (
3741
3791
  // Node's web stream type and the DOM one do not overlap for tsc on
@@ -3779,7 +3829,7 @@ ${runtimeTag}`;
3779
3829
  }
3780
3830
 
3781
3831
  // src/routes/lint.ts
3782
- import { readFileSync as readFileSync8 } from "fs";
3832
+ import { readFileSync as readFileSync9 } from "fs";
3783
3833
  import { join as join9 } from "path";
3784
3834
  async function collectProjectFindings(adapter, projectDir) {
3785
3835
  const findings = [];
@@ -3798,7 +3848,7 @@ async function lintUncoveredHtml(adapter, projectDir, htmlFiles, coveredFiles) {
3798
3848
  const findings = [];
3799
3849
  for (const file of htmlFiles) {
3800
3850
  if (coveredFiles.has(file)) continue;
3801
- const content = readFileSync8(join9(projectDir, file), "utf-8");
3851
+ const content = readFileSync9(join9(projectDir, file), "utf-8");
3802
3852
  const result = await adapter.lint(content, { filePath: file, isSubComposition: true });
3803
3853
  for (const finding of result?.findings ?? []) {
3804
3854
  findings.push({ ...finding, file });
@@ -3831,7 +3881,7 @@ function registerLintRoutes(api, adapter) {
3831
3881
 
3832
3882
  // src/routes/render.ts
3833
3883
  import { streamSSE } from "hono/streaming";
3834
- import { existsSync as existsSync6, readFileSync as readFileSync9, mkdirSync as mkdirSync4, unlinkSync as unlinkSync3, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
3884
+ import { existsSync as existsSync6, readFileSync as readFileSync10, mkdirSync as mkdirSync4, unlinkSync as unlinkSync3, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
3835
3885
  import { join as join10 } from "path";
3836
3886
  import { VALID_CANVAS_RESOLUTIONS } from "@hyperframes/parsers";
3837
3887
  import { formatRenderOutputTimestamp, parseFps } from "@hyperframes/core";
@@ -3969,7 +4019,7 @@ function registerRenderRoutes(api, adapter) {
3969
4019
  }
3970
4020
  const contentType = renderContentType(job.outputPath);
3971
4021
  const filename = job.outputPath.split("/").pop() ?? `render.mp4`;
3972
- const content = readFileSync9(job.outputPath);
4022
+ const content = readFileSync10(job.outputPath);
3973
4023
  return new Response(content, {
3974
4024
  headers: {
3975
4025
  "Content-Type": contentType,
@@ -3987,7 +4037,7 @@ function registerRenderRoutes(api, adapter) {
3987
4037
  }
3988
4038
  const contentType = renderContentType(job.outputPath);
3989
4039
  const filename = job.outputPath.split("/").pop() ?? `render.mp4`;
3990
- const content = readFileSync9(job.outputPath);
4040
+ const content = readFileSync10(job.outputPath);
3991
4041
  return new Response(content, {
3992
4042
  headers: {
3993
4043
  "Content-Type": contentType,
@@ -4020,7 +4070,7 @@ function registerRenderRoutes(api, adapter) {
4020
4070
  if (!fp) return c.json({ error: "forbidden" }, 403);
4021
4071
  if (!existsSync6(fp)) return c.json({ error: "not found" }, 404);
4022
4072
  const contentType = renderContentType(fp);
4023
- const content = readFileSync9(fp);
4073
+ const content = readFileSync10(fp);
4024
4074
  return new Response(content, {
4025
4075
  headers: {
4026
4076
  "Content-Type": contentType,
@@ -4044,7 +4094,7 @@ function registerRenderRoutes(api, adapter) {
4044
4094
  let durationMs;
4045
4095
  if (existsSync6(metaPath)) {
4046
4096
  try {
4047
- const meta = JSON.parse(readFileSync9(metaPath, "utf-8"));
4097
+ const meta = JSON.parse(readFileSync10(metaPath, "utf-8"));
4048
4098
  if (meta.status === "failed" && !existsSync6(fp)) status = "failed";
4049
4099
  if (meta.durationMs) durationMs = meta.durationMs;
4050
4100
  } catch {
@@ -4078,13 +4128,13 @@ function registerRenderRoutes(api, adapter) {
4078
4128
  import {
4079
4129
  existsSync as existsSync7,
4080
4130
  mkdirSync as mkdirSync5,
4081
- readFileSync as readFileSync10,
4131
+ readFileSync as readFileSync11,
4082
4132
  readdirSync as readdirSync6,
4083
4133
  renameSync as renameSync3,
4084
4134
  rmSync as rmSync4,
4085
4135
  statSync as statSync5,
4086
4136
  unlinkSync as unlinkSync4,
4087
- writeFileSync as writeFileSync6
4137
+ writeFileSync as writeFileSync7
4088
4138
  } from "fs";
4089
4139
  import { join as join11 } from "path";
4090
4140
  import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
@@ -4233,7 +4283,7 @@ function pruneThumbnailCache(cacheDir, protectedPaths, now = Date.now()) {
4233
4283
  function writeThumbnailAtomically(path, buffer) {
4234
4284
  const temporaryPath = `${path}.${process.pid}.${randomUUID3()}.tmp`;
4235
4285
  try {
4236
- writeFileSync6(temporaryPath, buffer, { flag: "wx" });
4286
+ writeFileSync7(temporaryPath, buffer, { flag: "wx" });
4237
4287
  renameSync3(temporaryPath, path);
4238
4288
  } finally {
4239
4289
  rmSync4(temporaryPath, { force: true });
@@ -4271,7 +4321,7 @@ function registerThumbnailRoutes(api, adapter) {
4271
4321
  let sourceKey = "";
4272
4322
  const htmlFile = join11(project.dir, compPath);
4273
4323
  if (existsSync7(htmlFile)) {
4274
- const html = readFileSync10(htmlFile, "utf-8");
4324
+ const html = readFileSync11(htmlFile, "utf-8");
4275
4325
  sourceKey = `_${createHash4("sha1").update(html).digest("hex").slice(0, 16)}`;
4276
4326
  sourceMtime = Math.round(statSync5(htmlFile).mtimeMs);
4277
4327
  if (!vpWidth) {
@@ -4284,14 +4334,14 @@ function registerThumbnailRoutes(api, adapter) {
4284
4334
  const manualEditsFile = join11(project.dir, STUDIO_MANUAL_EDITS_PATH);
4285
4335
  let manualEditsKey = "";
4286
4336
  if (existsSync7(manualEditsFile)) {
4287
- const manualEditsContent = readFileSync10(manualEditsFile, "utf-8");
4337
+ const manualEditsContent = readFileSync11(manualEditsFile, "utf-8");
4288
4338
  manualEditsKey = `_${createHash4("sha1").update(manualEditsContent).digest("hex").slice(0, 16)}`;
4289
4339
  sourceMtime = Math.max(sourceMtime, Math.round(statSync5(manualEditsFile).mtimeMs));
4290
4340
  }
4291
4341
  const motionFile = join11(project.dir, STUDIO_MOTION_PATH);
4292
4342
  let motionKey = "";
4293
4343
  if (existsSync7(motionFile)) {
4294
- const motionContent = readFileSync10(motionFile, "utf-8");
4344
+ const motionContent = readFileSync11(motionFile, "utf-8");
4295
4345
  motionKey = `_${createHash4("sha1").update(motionContent).digest("hex").slice(0, 16)}`;
4296
4346
  sourceMtime = Math.max(sourceMtime, Math.round(statSync5(motionFile).mtimeMs));
4297
4347
  }
@@ -4313,7 +4363,7 @@ function registerThumbnailRoutes(api, adapter) {
4313
4363
  );
4314
4364
  }
4315
4365
  if (existsSync7(cachePath)) {
4316
- return new Response(new Uint8Array(readFileSync10(cachePath)), {
4366
+ return new Response(new Uint8Array(readFileSync11(cachePath)), {
4317
4367
  headers: { "Content-Type": contentType, "Cache-Control": "no-cache" }
4318
4368
  });
4319
4369
  }
@@ -4368,7 +4418,7 @@ function registerThumbnailRoutes(api, adapter) {
4368
4418
  }
4369
4419
 
4370
4420
  // src/routes/waveform.ts
4371
- import { existsSync as existsSync8, readFileSync as readFileSync11, statSync as statSync6 } from "fs";
4421
+ import { existsSync as existsSync8, readFileSync as readFileSync12, statSync as statSync6 } from "fs";
4372
4422
  import { join as join12 } from "path";
4373
4423
  function registerWaveformRoutes(api, adapter) {
4374
4424
  api.get("/projects/:id/waveform/*", async (c) => {
@@ -4384,7 +4434,7 @@ function registerWaveformRoutes(api, adapter) {
4384
4434
  const cachePath = join12(cacheDir, buildWaveformCacheKey(assetPath, stats));
4385
4435
  try {
4386
4436
  if (isWaveformCacheDirectory(cacheDir) && existsSync8(cachePath)) {
4387
- const peaks2 = JSON.parse(readFileSync11(cachePath, "utf-8"));
4437
+ const peaks2 = JSON.parse(readFileSync12(cachePath, "utf-8"));
4388
4438
  return c.json({ peaks: peaks2 });
4389
4439
  }
4390
4440
  } catch {
@@ -4870,14 +4920,14 @@ function registerMediaRoutes(api, adapter, options = {}) {
4870
4920
  }
4871
4921
 
4872
4922
  // src/routes/globalAssets.ts
4873
- import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
4923
+ import { existsSync as existsSync10, readFileSync as readFileSync13 } from "fs";
4874
4924
  import { homedir } from "os";
4875
4925
  import { join as join14 } from "path";
4876
4926
  function readGlobalAssets(home = homedir()) {
4877
4927
  const manifestPath = join14(home, ".media", "manifest.jsonl");
4878
4928
  if (!existsSync10(manifestPath)) return [];
4879
4929
  const out = [];
4880
- for (const line of readFileSync12(manifestPath, "utf8").split("\n")) {
4930
+ for (const line of readFileSync13(manifestPath, "utf8").split("\n")) {
4881
4931
  if (!line.trim()) continue;
4882
4932
  try {
4883
4933
  const rec = JSON.parse(line);
@@ -4984,6 +5034,7 @@ export {
4984
5034
  STUDIO_MANUAL_EDITS_PATH,
4985
5035
  STUDIO_MOTION_PATH,
4986
5036
  affectsProjectSignature,
5037
+ applyFileMutations,
4987
5038
  buildSubCompositionHtml,
4988
5039
  consumeFileWriteReceipt,
4989
5040
  createBackgroundRemovalJob,
@@ -4997,6 +5048,9 @@ export {
4997
5048
  getMimeType,
4998
5049
  identifyFileWrite,
4999
5050
  isSafePath,
5051
+ patchElementInHtml,
5052
+ removeElementFromHtml,
5053
+ splitElementInHtml,
5000
5054
  thumbnailDeviceScaleFactor,
5001
5055
  walkDir
5002
5056
  };