@hyperframes/studio-server 0.8.59 → 0.8.61
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 +77 -59
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -298,15 +298,15 @@ import { bodyLimit } from "hono/body-limit";
|
|
|
298
298
|
import {
|
|
299
299
|
closeSync,
|
|
300
300
|
existsSync as existsSync3,
|
|
301
|
-
ftruncateSync,
|
|
302
301
|
openSync,
|
|
303
302
|
readFileSync as readFileSync5,
|
|
304
|
-
writeFileSync as
|
|
303
|
+
writeFileSync as writeFileSync4,
|
|
305
304
|
writeSync,
|
|
306
305
|
mkdirSync as mkdirSync3,
|
|
307
306
|
unlinkSync as unlinkSync2,
|
|
308
307
|
rmSync as rmSync3,
|
|
309
|
-
statSync as
|
|
308
|
+
statSync as statSync3,
|
|
309
|
+
fstatSync,
|
|
310
310
|
renameSync as renameSync2,
|
|
311
311
|
readdirSync as readdirSync4
|
|
312
312
|
} from "fs";
|
|
@@ -358,6 +358,24 @@ function isAudioFile(name) {
|
|
|
358
358
|
return (getMimeType(name) ?? "").startsWith("audio/");
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
+
// src/helpers/atomicFile.ts
|
|
362
|
+
import * as fs from "fs";
|
|
363
|
+
import { randomUUID } from "crypto";
|
|
364
|
+
function replaceFileAtomically(filePath, content, mode, operations = fs) {
|
|
365
|
+
const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
366
|
+
try {
|
|
367
|
+
operations.writeFileSync(tempPath, content, { encoding: "utf-8", mode });
|
|
368
|
+
operations.chmodSync(tempPath, mode);
|
|
369
|
+
operations.renameSync(tempPath, filePath);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
try {
|
|
372
|
+
operations.unlinkSync(tempPath);
|
|
373
|
+
} catch {
|
|
374
|
+
}
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
361
379
|
// src/helpers/waveform.ts
|
|
362
380
|
import { spawn } from "child_process";
|
|
363
381
|
import {
|
|
@@ -592,7 +610,7 @@ function pruneBackups(backupDir, backupKey, keepPerFile) {
|
|
|
592
610
|
}
|
|
593
611
|
|
|
594
612
|
// src/helpers/fileVersion.ts
|
|
595
|
-
import { createHash as createHash2, randomUUID } from "crypto";
|
|
613
|
+
import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
|
|
596
614
|
var RECEIPT_TTL_MS = 1e4;
|
|
597
615
|
var receipts = /* @__PURE__ */ new Map();
|
|
598
616
|
function fileContentVersion(content) {
|
|
@@ -600,7 +618,7 @@ function fileContentVersion(content) {
|
|
|
600
618
|
}
|
|
601
619
|
function createWriteToken(requestToken) {
|
|
602
620
|
const token = requestToken?.trim();
|
|
603
|
-
return token && token.length <= 200 ? token :
|
|
621
|
+
return token && token.length <= 200 ? token : randomUUID2();
|
|
604
622
|
}
|
|
605
623
|
function recordFileWriteReceipt(absPath, receipt) {
|
|
606
624
|
const now = Date.now();
|
|
@@ -635,8 +653,8 @@ function identifyFileWrite(absPath, expectedVersion) {
|
|
|
635
653
|
var consumeFileWriteReceipt = identifyFileWrite;
|
|
636
654
|
|
|
637
655
|
// src/helpers/applyFileMutations.ts
|
|
638
|
-
import { readFileSync as readFileSync3,
|
|
639
|
-
function applyFileMutations(projectDir, mutations, requestToken, writeFile =
|
|
656
|
+
import { readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
657
|
+
function applyFileMutations(projectDir, mutations, requestToken, writeFile = (path, content) => replaceFileAtomically(path, content, statSync2(path).mode)) {
|
|
640
658
|
const prepared = mutations.map((mutation) => ({
|
|
641
659
|
...mutation,
|
|
642
660
|
before: mutation.before ?? readFileSync3(mutation.absPath, "utf-8")
|
|
@@ -743,7 +761,7 @@ import { parseHTML as parseHTML2 } from "linkedom";
|
|
|
743
761
|
|
|
744
762
|
// src/helpers/compositionInsertion.ts
|
|
745
763
|
import { existsSync as existsSync2, readFileSync as readFileSync4, realpathSync } from "fs";
|
|
746
|
-
import { randomUUID as
|
|
764
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
747
765
|
import { dirname as dirname2, relative as relative3, resolve as resolve2, sep as sep2 } from "path";
|
|
748
766
|
import { parseHTML } from "linkedom";
|
|
749
767
|
var CompositionInsertionError = class extends Error {
|
|
@@ -907,7 +925,7 @@ function insertCompositionIntoSource(input) {
|
|
|
907
925
|
const host = document.createElement("div");
|
|
908
926
|
host.id = hostId;
|
|
909
927
|
host.className = "clip";
|
|
910
|
-
host.setAttribute("data-hf-id", `hf-${
|
|
928
|
+
host.setAttribute("data-hf-id", `hf-${randomUUID3()}`);
|
|
911
929
|
host.setAttribute("data-composition-id", hostId);
|
|
912
930
|
host.setAttribute("data-composition-src", relativeSourcePath(targetAbs, sourceAbs));
|
|
913
931
|
host.setAttribute("data-start", String(Math.round(input.start * 100) / 100));
|
|
@@ -1000,13 +1018,18 @@ async function resolveProjectPath(c, adapter, pathPrefix, opts) {
|
|
|
1000
1018
|
if (!project) {
|
|
1001
1019
|
return { error: c.json({ error: "not found" }, 404) };
|
|
1002
1020
|
}
|
|
1021
|
+
if (!existsSync3(project.dir)) {
|
|
1022
|
+
return {
|
|
1023
|
+
error: c.json({ error: "not found", why: "project_dir_missing" }, 404)
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1003
1026
|
const filePath = decodeURIComponent(c.req.path.replace(pathPrefix(project.id), ""));
|
|
1004
1027
|
if (filePath.includes("\0")) {
|
|
1005
|
-
return { error: c.json({ error: "forbidden" }, 403) };
|
|
1028
|
+
return { error: c.json({ error: "forbidden", why: "nul" }, 403) };
|
|
1006
1029
|
}
|
|
1007
1030
|
const absPath = resolveWithinProject(project.dir, filePath);
|
|
1008
1031
|
if (!absPath) {
|
|
1009
|
-
return { error: c.json({ error: "forbidden" }, 403) };
|
|
1032
|
+
return { error: c.json({ error: "forbidden", why: "outside_project" }, 403) };
|
|
1010
1033
|
}
|
|
1011
1034
|
if (opts?.mustExist && !existsSync3(absPath)) {
|
|
1012
1035
|
return { error: c.json({ error: "not found" }, 404) };
|
|
@@ -1066,7 +1089,7 @@ function foldElementPatches(originalContent, patches) {
|
|
|
1066
1089
|
}
|
|
1067
1090
|
return { content, matched };
|
|
1068
1091
|
}
|
|
1069
|
-
function commitElementPatchBatches(projectDir, batches, writeFile =
|
|
1092
|
+
function commitElementPatchBatches(projectDir, batches, writeFile = (path, content) => replaceFileAtomically(path, content, statSync3(path).mode), requestToken) {
|
|
1070
1093
|
const resolvedPaths = /* @__PURE__ */ new Set();
|
|
1071
1094
|
const prepared = [];
|
|
1072
1095
|
for (const batch of batches) {
|
|
@@ -1127,7 +1150,7 @@ function commitElementPatchBatchesWithReceipts(c, projectDir, batches) {
|
|
|
1127
1150
|
return commitElementPatchBatches(
|
|
1128
1151
|
projectDir,
|
|
1129
1152
|
batches,
|
|
1130
|
-
|
|
1153
|
+
writeFileSync4,
|
|
1131
1154
|
c.req.header("X-Hyperframes-Write-Token")
|
|
1132
1155
|
);
|
|
1133
1156
|
}
|
|
@@ -1138,7 +1161,7 @@ function recordMutationReceipt(c, filePath, absPath, html) {
|
|
|
1138
1161
|
return { version, writeToken };
|
|
1139
1162
|
}
|
|
1140
1163
|
function writeFileWithReceipt(c, filePath, absPath, html) {
|
|
1141
|
-
|
|
1164
|
+
replaceFileAtomically(absPath, html, statSync3(absPath).mode);
|
|
1142
1165
|
return recordMutationReceipt(c, filePath, absPath, html);
|
|
1143
1166
|
}
|
|
1144
1167
|
function writeMutationResult(c, projectDir, filePath, absPath, html) {
|
|
@@ -1227,7 +1250,7 @@ function updateReferences(projectDir, oldPath, newPath) {
|
|
|
1227
1250
|
if (!content.includes(oldPath)) continue;
|
|
1228
1251
|
const updated = content.split(oldPath).join(newPath);
|
|
1229
1252
|
if (updated !== content) {
|
|
1230
|
-
|
|
1253
|
+
replaceFileAtomically(file, updated, statSync3(file).mode);
|
|
1231
1254
|
updatedCount++;
|
|
1232
1255
|
}
|
|
1233
1256
|
}
|
|
@@ -2347,7 +2370,7 @@ async function processUploadedFiles(formData, targetDir, projectDir) {
|
|
|
2347
2370
|
try {
|
|
2348
2371
|
const fd = openSync(finalPath, "wx");
|
|
2349
2372
|
try {
|
|
2350
|
-
|
|
2373
|
+
writeFileSync4(fd, buffer);
|
|
2351
2374
|
} finally {
|
|
2352
2375
|
closeSync(fd);
|
|
2353
2376
|
}
|
|
@@ -2475,10 +2498,12 @@ function registerFileRoutes(api, adapter) {
|
|
|
2475
2498
|
}
|
|
2476
2499
|
backup = snapshotBeforeWrite(res.project.dir, res.absPath);
|
|
2477
2500
|
if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
|
|
2478
|
-
|
|
2479
|
-
writeSync(fd, body, 0, body.length, 0);
|
|
2480
|
-
} finally {
|
|
2501
|
+
const mode = fstatSync(fd).mode;
|
|
2481
2502
|
closeSync(fd);
|
|
2503
|
+
fd = null;
|
|
2504
|
+
replaceFileAtomically(res.absPath, body, mode);
|
|
2505
|
+
} finally {
|
|
2506
|
+
if (fd !== null) closeSync(fd);
|
|
2482
2507
|
}
|
|
2483
2508
|
}
|
|
2484
2509
|
const version = fileContentVersion(body);
|
|
@@ -2499,7 +2524,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
2499
2524
|
ensureDir(res.absPath);
|
|
2500
2525
|
const body = Buffer.from(await c.req.arrayBuffer());
|
|
2501
2526
|
try {
|
|
2502
|
-
|
|
2527
|
+
writeFileSync4(res.absPath, body, { flag: "wx" });
|
|
2503
2528
|
} catch (error) {
|
|
2504
2529
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
|
|
2505
2530
|
throw error;
|
|
@@ -2511,7 +2536,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
2511
2536
|
api.delete("/projects/:id/files/*", async (c) => {
|
|
2512
2537
|
const res = await resolveProjectFile(c, adapter, { mustExist: true });
|
|
2513
2538
|
if ("error" in res) return res.error;
|
|
2514
|
-
const stat =
|
|
2539
|
+
const stat = statSync3(res.absPath);
|
|
2515
2540
|
const backup = snapshotBeforeWrite(res.project.dir, res.absPath);
|
|
2516
2541
|
if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
|
|
2517
2542
|
if (stat.isDirectory()) {
|
|
@@ -2701,7 +2726,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
2701
2726
|
const written = [];
|
|
2702
2727
|
try {
|
|
2703
2728
|
for (const file of prepared) {
|
|
2704
|
-
|
|
2729
|
+
replaceFileAtomically(file.absPath, file.after, statSync3(file.absPath).mode);
|
|
2705
2730
|
written.push(file);
|
|
2706
2731
|
recordFileWriteReceipt(file.absPath, {
|
|
2707
2732
|
path: file.path,
|
|
@@ -2718,7 +2743,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
2718
2743
|
conflicts.push(file.path);
|
|
2719
2744
|
continue;
|
|
2720
2745
|
}
|
|
2721
|
-
|
|
2746
|
+
replaceFileAtomically(file.absPath, file.before, statSync3(file.absPath).mode);
|
|
2722
2747
|
recordFileWriteReceipt(file.absPath, {
|
|
2723
2748
|
path: file.path,
|
|
2724
2749
|
version: fileContentVersion(file.before),
|
|
@@ -3045,7 +3070,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
3045
3070
|
return c.json({ error: "forbidden" }, 403);
|
|
3046
3071
|
}
|
|
3047
3072
|
ensureDir(destAbs);
|
|
3048
|
-
|
|
3073
|
+
writeFileSync4(destAbs, readFileSync5(srcAbs));
|
|
3049
3074
|
return c.json({ ok: true, path: copyPath }, 201);
|
|
3050
3075
|
});
|
|
3051
3076
|
const MAX_UPLOAD_BYTES = 500 * 1024 * 1024;
|
|
@@ -3138,13 +3163,13 @@ function registerFileRoutes(api, adapter) {
|
|
|
3138
3163
|
if (current !== body.expected) {
|
|
3139
3164
|
return c.json({ ok: true, restored: false, conflict: true });
|
|
3140
3165
|
}
|
|
3141
|
-
|
|
3166
|
+
replaceFileAtomically(res.absPath, body.restore, statSync3(res.absPath).mode);
|
|
3142
3167
|
return c.json({ ok: true, restored: true, conflict: false });
|
|
3143
3168
|
});
|
|
3144
3169
|
}
|
|
3145
3170
|
|
|
3146
3171
|
// src/routes/preview.ts
|
|
3147
|
-
import { createReadStream, existsSync as existsSync5, readFileSync as readFileSync8, statSync as
|
|
3172
|
+
import { createReadStream, existsSync as existsSync5, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
|
|
3148
3173
|
import { Readable } from "stream";
|
|
3149
3174
|
import { join as join8, resolve as resolve4 } from "path";
|
|
3150
3175
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -3335,16 +3360,7 @@ import { ensureHfIds as ensureHfIds2 } from "@hyperframes/parsers/hf-ids";
|
|
|
3335
3360
|
|
|
3336
3361
|
// src/helpers/hfIdPersist.ts
|
|
3337
3362
|
import { ensureHfIds } from "@hyperframes/parsers/hf-ids";
|
|
3338
|
-
import {
|
|
3339
|
-
closeSync as closeSync2,
|
|
3340
|
-
constants,
|
|
3341
|
-
fstatSync,
|
|
3342
|
-
ftruncateSync as ftruncateSync2,
|
|
3343
|
-
openSync as openSync2,
|
|
3344
|
-
readFileSync as readFileSync7,
|
|
3345
|
-
writeFileSync as writeFileSync6,
|
|
3346
|
-
writeSync as writeSync2
|
|
3347
|
-
} from "fs";
|
|
3363
|
+
import { closeSync as closeSync2, constants, fstatSync as fstatSync2, openSync as openSync2, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
|
|
3348
3364
|
function persistHfIdsIfNeeded(filePath, html) {
|
|
3349
3365
|
const normalized = ensureHfIds(html);
|
|
3350
3366
|
const idsBefore = (html.match(/\bdata-hf-id=/g) ?? []).length;
|
|
@@ -3353,7 +3369,7 @@ function persistHfIdsIfNeeded(filePath, html) {
|
|
|
3353
3369
|
try {
|
|
3354
3370
|
const current = readFileSync7(filePath, "utf-8");
|
|
3355
3371
|
if (current === html) {
|
|
3356
|
-
|
|
3372
|
+
replaceFileAtomically(filePath, normalized, statSync4(filePath).mode);
|
|
3357
3373
|
}
|
|
3358
3374
|
} catch (err) {
|
|
3359
3375
|
console.warn("[hyperframes] persistHfIdsIfNeeded: failed to write ids to disk:", err);
|
|
@@ -3378,21 +3394,23 @@ function stampFileHfIds(filePath) {
|
|
|
3378
3394
|
}
|
|
3379
3395
|
if (fd === null) return null;
|
|
3380
3396
|
try {
|
|
3381
|
-
if (!
|
|
3397
|
+
if (!fstatSync2(fd).isFile()) return null;
|
|
3382
3398
|
const html = readFileSync7(fd, "utf-8");
|
|
3383
3399
|
const normalized = ensureHfIds(html);
|
|
3384
3400
|
const idsBefore = (html.match(/\bdata-hf-id=/g) ?? []).length;
|
|
3385
3401
|
const idsAfter = (normalized.match(/\bdata-hf-id=/g) ?? []).length;
|
|
3386
3402
|
if (writable && idsAfter > idsBefore) {
|
|
3387
|
-
|
|
3388
|
-
|
|
3403
|
+
const mode = fstatSync2(fd).mode;
|
|
3404
|
+
closeSync2(fd);
|
|
3405
|
+
fd = null;
|
|
3406
|
+
replaceFileAtomically(filePath, normalized, mode);
|
|
3389
3407
|
}
|
|
3390
3408
|
return normalized;
|
|
3391
3409
|
} catch (err) {
|
|
3392
3410
|
console.warn("[hyperframes] stampFileHfIds: failed to stamp ids:", err);
|
|
3393
3411
|
return null;
|
|
3394
3412
|
} finally {
|
|
3395
|
-
closeSync2(fd);
|
|
3413
|
+
if (fd !== null) closeSync2(fd);
|
|
3396
3414
|
}
|
|
3397
3415
|
}
|
|
3398
3416
|
|
|
@@ -3699,7 +3717,7 @@ ${runtimeTag}`;
|
|
|
3699
3717
|
c.req.path.replace(`/projects/${project.id}/preview/comp/`, "").split("?")[0] ?? ""
|
|
3700
3718
|
);
|
|
3701
3719
|
const compFile = resolveWithinProject(project.dir, compPath);
|
|
3702
|
-
if (!compFile || !existsSync5(compFile) || !
|
|
3720
|
+
if (!compFile || !existsSync5(compFile) || !statSync5(compFile).isFile()) {
|
|
3703
3721
|
return c.text("not found", 404);
|
|
3704
3722
|
}
|
|
3705
3723
|
const compPathHash = createHash3("sha1").update(compPath).digest("hex");
|
|
@@ -3739,7 +3757,7 @@ ${runtimeTag}`;
|
|
|
3739
3757
|
if (!file) {
|
|
3740
3758
|
return c.text("not found", 404);
|
|
3741
3759
|
}
|
|
3742
|
-
const stat = existsSync5(file) ?
|
|
3760
|
+
const stat = existsSync5(file) ? statSync5(file) : null;
|
|
3743
3761
|
if (!stat?.isFile()) {
|
|
3744
3762
|
return c.text("not found", 404);
|
|
3745
3763
|
}
|
|
@@ -3789,7 +3807,7 @@ ${runtimeTag}`;
|
|
|
3789
3807
|
servedContentType = PROXY_VARIANT_CONFIG[proxyVariant].contentType;
|
|
3790
3808
|
}
|
|
3791
3809
|
const textBuffer = isText ? Buffer.from(readFileSync8(file, "utf-8"), "utf-8") : null;
|
|
3792
|
-
const totalSize = textBuffer ? textBuffer.length :
|
|
3810
|
+
const totalSize = textBuffer ? textBuffer.length : statSync5(servedPath).size;
|
|
3793
3811
|
const bodyFor = (start, end) => textBuffer ? new Uint8Array(textBuffer.subarray(start, end + 1)) : (
|
|
3794
3812
|
// Node's web stream type and the DOM one do not overlap for tsc on
|
|
3795
3813
|
// every platform's lib set; the double cast is the documented bridge.
|
|
@@ -3884,7 +3902,7 @@ function registerLintRoutes(api, adapter) {
|
|
|
3884
3902
|
|
|
3885
3903
|
// src/routes/render.ts
|
|
3886
3904
|
import { streamSSE } from "hono/streaming";
|
|
3887
|
-
import { existsSync as existsSync6, readFileSync as readFileSync10, mkdirSync as mkdirSync4, unlinkSync as unlinkSync3, readdirSync as readdirSync5, statSync as
|
|
3905
|
+
import { existsSync as existsSync6, readFileSync as readFileSync10, mkdirSync as mkdirSync4, unlinkSync as unlinkSync3, readdirSync as readdirSync5, statSync as statSync6 } from "fs";
|
|
3888
3906
|
import { join as join10 } from "path";
|
|
3889
3907
|
import { VALID_CANVAS_RESOLUTIONS } from "@hyperframes/parsers";
|
|
3890
3908
|
import { formatRenderOutputTimestamp, parseFps } from "@hyperframes/core";
|
|
@@ -4090,7 +4108,7 @@ function registerRenderRoutes(api, adapter) {
|
|
|
4090
4108
|
if (!existsSync6(rendersDir)) return c.json({ renders: [] });
|
|
4091
4109
|
const files = readdirSync5(rendersDir).filter((f) => f.endsWith(".mp4") || f.endsWith(".webm") || f.endsWith(".mov")).map((f) => {
|
|
4092
4110
|
const fp = join10(rendersDir, f);
|
|
4093
|
-
const stat =
|
|
4111
|
+
const stat = statSync6(fp);
|
|
4094
4112
|
const rid = f.replace(/\.(mp4|webm|mov)$/, "");
|
|
4095
4113
|
const metaPath = join10(rendersDir, `${rid}.meta.json`);
|
|
4096
4114
|
let status = "complete";
|
|
@@ -4135,12 +4153,12 @@ import {
|
|
|
4135
4153
|
readdirSync as readdirSync6,
|
|
4136
4154
|
renameSync as renameSync3,
|
|
4137
4155
|
rmSync as rmSync4,
|
|
4138
|
-
statSync as
|
|
4156
|
+
statSync as statSync7,
|
|
4139
4157
|
unlinkSync as unlinkSync4,
|
|
4140
|
-
writeFileSync as
|
|
4158
|
+
writeFileSync as writeFileSync5
|
|
4141
4159
|
} from "fs";
|
|
4142
4160
|
import { join as join11 } from "path";
|
|
4143
|
-
import { createHash as createHash4, randomUUID as
|
|
4161
|
+
import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
|
|
4144
4162
|
|
|
4145
4163
|
// src/routes/thumbnailGenerationCoordinator.ts
|
|
4146
4164
|
var ThumbnailGenerationCoordinator = class {
|
|
@@ -4258,7 +4276,7 @@ function pruneThumbnailCache(cacheDir, protectedPaths, now = Date.now()) {
|
|
|
4258
4276
|
if (!entry.isFile()) return [];
|
|
4259
4277
|
const path = join11(cacheDir, entry.name);
|
|
4260
4278
|
try {
|
|
4261
|
-
const stats =
|
|
4279
|
+
const stats = statSync7(path);
|
|
4262
4280
|
return [{ path, bytes: stats.size, mtimeMs: stats.mtimeMs }];
|
|
4263
4281
|
} catch {
|
|
4264
4282
|
return [];
|
|
@@ -4284,9 +4302,9 @@ function pruneThumbnailCache(cacheDir, protectedPaths, now = Date.now()) {
|
|
|
4284
4302
|
}
|
|
4285
4303
|
}
|
|
4286
4304
|
function writeThumbnailAtomically(path, buffer) {
|
|
4287
|
-
const temporaryPath = `${path}.${process.pid}.${
|
|
4305
|
+
const temporaryPath = `${path}.${process.pid}.${randomUUID4()}.tmp`;
|
|
4288
4306
|
try {
|
|
4289
|
-
|
|
4307
|
+
writeFileSync5(temporaryPath, buffer, { flag: "wx" });
|
|
4290
4308
|
renameSync3(temporaryPath, path);
|
|
4291
4309
|
} finally {
|
|
4292
4310
|
rmSync4(temporaryPath, { force: true });
|
|
@@ -4326,7 +4344,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4326
4344
|
if (existsSync7(htmlFile)) {
|
|
4327
4345
|
const html = readFileSync11(htmlFile, "utf-8");
|
|
4328
4346
|
sourceKey = `_${createHash4("sha1").update(html).digest("hex").slice(0, 16)}`;
|
|
4329
|
-
sourceMtime = Math.round(
|
|
4347
|
+
sourceMtime = Math.round(statSync7(htmlFile).mtimeMs);
|
|
4330
4348
|
if (!vpWidth) {
|
|
4331
4349
|
const wMatch = html.match(/data-width=["'](\d+)["']/);
|
|
4332
4350
|
const hMatch = html.match(/data-height=["'](\d+)["']/);
|
|
@@ -4339,14 +4357,14 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4339
4357
|
if (existsSync7(manualEditsFile)) {
|
|
4340
4358
|
const manualEditsContent = readFileSync11(manualEditsFile, "utf-8");
|
|
4341
4359
|
manualEditsKey = `_${createHash4("sha1").update(manualEditsContent).digest("hex").slice(0, 16)}`;
|
|
4342
|
-
sourceMtime = Math.max(sourceMtime, Math.round(
|
|
4360
|
+
sourceMtime = Math.max(sourceMtime, Math.round(statSync7(manualEditsFile).mtimeMs));
|
|
4343
4361
|
}
|
|
4344
4362
|
const motionFile = join11(project.dir, STUDIO_MOTION_PATH);
|
|
4345
4363
|
let motionKey = "";
|
|
4346
4364
|
if (existsSync7(motionFile)) {
|
|
4347
4365
|
const motionContent = readFileSync11(motionFile, "utf-8");
|
|
4348
4366
|
motionKey = `_${createHash4("sha1").update(motionContent).digest("hex").slice(0, 16)}`;
|
|
4349
|
-
sourceMtime = Math.max(sourceMtime, Math.round(
|
|
4367
|
+
sourceMtime = Math.max(sourceMtime, Math.round(statSync7(motionFile).mtimeMs));
|
|
4350
4368
|
}
|
|
4351
4369
|
const previewUrl = compPath === "index.html" ? `http://${c.req.header("host")}/api/projects/${project.id}/preview` : `http://${c.req.header("host")}/api/projects/${project.id}/preview/comp/${compPath}`;
|
|
4352
4370
|
const cacheDir = join11(project.dir, ".thumbnails");
|
|
@@ -4421,7 +4439,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4421
4439
|
}
|
|
4422
4440
|
|
|
4423
4441
|
// src/routes/waveform.ts
|
|
4424
|
-
import { existsSync as existsSync8, readFileSync as readFileSync12, statSync as
|
|
4442
|
+
import { existsSync as existsSync8, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
|
|
4425
4443
|
import { join as join12 } from "path";
|
|
4426
4444
|
function registerWaveformRoutes(api, adapter) {
|
|
4427
4445
|
api.get("/projects/:id/waveform/*", async (c) => {
|
|
@@ -4431,7 +4449,7 @@ function registerWaveformRoutes(api, adapter) {
|
|
|
4431
4449
|
c.req.path.replace(`/projects/${project.id}/waveform/`, "").split("?")[0] ?? ""
|
|
4432
4450
|
);
|
|
4433
4451
|
const audioPath = join12(project.dir, assetPath);
|
|
4434
|
-
const stats =
|
|
4452
|
+
const stats = statSync8(audioPath, { throwIfNoEntry: false });
|
|
4435
4453
|
if (!stats) return c.json({ error: "file not found" }, 404);
|
|
4436
4454
|
const cacheDir = join12(project.dir, ".waveform-cache");
|
|
4437
4455
|
const cachePath = join12(cacheDir, buildWaveformCacheKey(assetPath, stats));
|
|
@@ -4457,7 +4475,7 @@ function registerWaveformRoutes(api, adapter) {
|
|
|
4457
4475
|
}
|
|
4458
4476
|
|
|
4459
4477
|
// src/routes/fonts.ts
|
|
4460
|
-
import { closeSync as closeSync3, constants as constants2, fstatSync as
|
|
4478
|
+
import { closeSync as closeSync3, constants as constants2, fstatSync as fstatSync3, openSync as openSync3, readSync } from "fs";
|
|
4461
4479
|
import {
|
|
4462
4480
|
collectFontFileEntries,
|
|
4463
4481
|
fontDirectories,
|
|
@@ -4569,7 +4587,7 @@ function registerFontRoutes(api) {
|
|
|
4569
4587
|
return c.json({ error: "font file not accessible" }, 404);
|
|
4570
4588
|
}
|
|
4571
4589
|
try {
|
|
4572
|
-
const stat =
|
|
4590
|
+
const stat = fstatSync3(fd);
|
|
4573
4591
|
if (stat.size > SYSTEM_FONT_SIZE_LIMIT) {
|
|
4574
4592
|
return c.json({ error: "font file too large" }, 413);
|
|
4575
4593
|
}
|