@hyperframes/studio-server 0.8.60 → 0.8.62

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
@@ -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 writeFileSync5,
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 statSync2,
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 : randomUUID();
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, writeFileSync as writeFileSync4 } from "fs";
639
- function applyFileMutations(projectDir, mutations, requestToken, writeFile = writeFileSync4) {
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 randomUUID2 } from "crypto";
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-${randomUUID2()}`);
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 = writeFileSync5, requestToken) {
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
- writeFileSync5,
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
- writeFileSync5(absPath, html, "utf-8");
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
- writeFileSync5(file, updated, "utf-8");
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
- writeFileSync5(fd, buffer);
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
- ftruncateSync(fd, 0);
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
- writeFileSync5(res.absPath, body, { flag: "wx" });
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 = statSync2(res.absPath);
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
- writeFileSync5(file.absPath, file.after, "utf-8");
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
- writeFileSync5(file.absPath, file.before, "utf-8");
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
- writeFileSync5(destAbs, readFileSync5(srcAbs));
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
- writeFileSync5(res.absPath, body.restore, "utf-8");
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 statSync3 } from "fs";
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
- writeFileSync6(filePath, normalized, "utf-8");
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 (!fstatSync(fd).isFile()) return null;
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
- ftruncateSync2(fd, 0);
3388
- writeSync2(fd, normalized, 0, "utf-8");
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) || !statSync3(compFile).isFile()) {
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) ? statSync3(file) : null;
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 : statSync3(servedPath).size;
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 statSync4 } from "fs";
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 = statSync4(fp);
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 statSync5,
4156
+ statSync as statSync7,
4139
4157
  unlinkSync as unlinkSync4,
4140
- writeFileSync as writeFileSync7
4158
+ writeFileSync as writeFileSync5
4141
4159
  } from "fs";
4142
4160
  import { join as join11 } from "path";
4143
- import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
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 = statSync5(path);
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}.${randomUUID3()}.tmp`;
4305
+ const temporaryPath = `${path}.${process.pid}.${randomUUID4()}.tmp`;
4288
4306
  try {
4289
- writeFileSync7(temporaryPath, buffer, { flag: "wx" });
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(statSync5(htmlFile).mtimeMs);
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(statSync5(manualEditsFile).mtimeMs));
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(statSync5(motionFile).mtimeMs));
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 statSync6 } from "fs";
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 = statSync6(audioPath, { throwIfNoEntry: false });
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 fstatSync2, openSync as openSync3, readSync } from "fs";
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 = fstatSync2(fd);
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
  }