@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.d.ts +21 -1
- package/dist/index.js +175 -121
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -297,8 +297,8 @@ import {
|
|
|
297
297
|
existsSync as existsSync3,
|
|
298
298
|
ftruncateSync,
|
|
299
299
|
openSync,
|
|
300
|
-
readFileSync as
|
|
301
|
-
writeFileSync as
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
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
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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 ??
|
|
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 =
|
|
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
|
|
3292
|
-
writeFileSync as
|
|
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 =
|
|
3351
|
+
const current = readFileSync7(filePath, "utf-8");
|
|
3302
3352
|
if (current === html) {
|
|
3303
|
-
|
|
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 =
|
|
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
|
|
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:
|
|
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:
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
};
|