@gallop.software/studio 2.3.85 → 2.3.87

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.
@@ -148,13 +148,16 @@ var DEFAULT_SIZES = {
148
148
  large: { width: 1400, suffix: "-lg", key: "lg" }
149
149
  };
150
150
  async function processImage(buffer, imageKey) {
151
- const sharpInstance = sharp(buffer);
152
- const metadata = await sharpInstance.metadata();
151
+ const rotatedBuffer = await sharp(buffer).rotate().toBuffer();
152
+ const metadata = await sharp(rotatedBuffer).metadata();
153
153
  const originalWidth = metadata.width || 0;
154
154
  const originalHeight = metadata.height || 0;
155
155
  const ratio = originalHeight / originalWidth;
156
156
  const keyWithoutSlash = imageKey.startsWith("/") ? imageKey.slice(1) : imageKey;
157
- const baseName = path3.basename(keyWithoutSlash, path3.extname(keyWithoutSlash));
157
+ const baseName = path3.basename(
158
+ keyWithoutSlash,
159
+ path3.extname(keyWithoutSlash)
160
+ );
158
161
  const ext = path3.extname(keyWithoutSlash).toLowerCase();
159
162
  const imageDir = path3.dirname(keyWithoutSlash);
160
163
  const imagesPath = getPublicPath("images", imageDir === "." ? "" : imageDir);
@@ -172,15 +175,15 @@ async function processImage(buffer, imageKey) {
172
175
  fullWidth = FULL_MAX_WIDTH;
173
176
  fullHeight = Math.round(FULL_MAX_WIDTH * ratio);
174
177
  if (isPng) {
175
- await sharp(buffer).resize(fullWidth, fullHeight).png({ quality: 85 }).toFile(fullPath);
178
+ await sharp(rotatedBuffer).resize(fullWidth, fullHeight).png({ quality: 85 }).toFile(fullPath);
176
179
  } else {
177
- await sharp(buffer).resize(fullWidth, fullHeight).jpeg({ quality: 85 }).toFile(fullPath);
180
+ await sharp(rotatedBuffer).resize(fullWidth, fullHeight).jpeg({ quality: 85 }).toFile(fullPath);
178
181
  }
179
182
  } else {
180
183
  if (isPng) {
181
- await sharp(buffer).png({ quality: 85 }).toFile(fullPath);
184
+ await sharp(rotatedBuffer).png({ quality: 85 }).toFile(fullPath);
182
185
  } else {
183
- await sharp(buffer).jpeg({ quality: 85 }).toFile(fullPath);
186
+ await sharp(rotatedBuffer).jpeg({ quality: 85 }).toFile(fullPath);
184
187
  }
185
188
  }
186
189
  entry.f = { w: fullWidth, h: fullHeight };
@@ -194,9 +197,9 @@ async function processImage(buffer, imageKey) {
194
197
  const sizeFilePath = imageDir === "." ? sizeFileName : `${imageDir}/${sizeFileName}`;
195
198
  const sizePath = getPublicPath("images", sizeFilePath);
196
199
  if (isPng) {
197
- await sharp(buffer).resize(maxWidth, newHeight).png({ quality: 80 }).toFile(sizePath);
200
+ await sharp(rotatedBuffer).resize(maxWidth, newHeight).png({ quality: 80 }).toFile(sizePath);
198
201
  } else {
199
- await sharp(buffer).resize(maxWidth, newHeight).jpeg({ quality: 80 }).toFile(sizePath);
202
+ await sharp(rotatedBuffer).resize(maxWidth, newHeight).jpeg({ quality: 80 }).toFile(sizePath);
200
203
  }
201
204
  entry[key] = { w: maxWidth, h: newHeight };
202
205
  }
@@ -1158,7 +1161,11 @@ async function cleanupEmptyFoldersRecursive(dir) {
1158
1161
  // src/handlers/images.ts
1159
1162
  import { promises as fs6 } from "fs";
1160
1163
  import path6 from "path";
1161
- import { S3Client as S3Client2, PutObjectCommand as PutObjectCommand2, DeleteObjectCommand as DeleteObjectCommand2 } from "@aws-sdk/client-s3";
1164
+ import {
1165
+ S3Client as S3Client2,
1166
+ PutObjectCommand as PutObjectCommand2,
1167
+ DeleteObjectCommand as DeleteObjectCommand2
1168
+ } from "@aws-sdk/client-s3";
1162
1169
  var cancelledOperations = /* @__PURE__ */ new Set();
1163
1170
  function cancelOperation(operationId) {
1164
1171
  cancelledOperations.add(operationId);
@@ -1178,7 +1185,9 @@ async function handleSync(request) {
1178
1185
  const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\/\s*$/, "");
1179
1186
  if (!accountId || !accessKeyId || !secretAccessKey || !bucketName || !publicUrl) {
1180
1187
  return jsonResponse(
1181
- { error: "R2 not configured. Set CLOUDFLARE_R2_* environment variables." },
1188
+ {
1189
+ error: "R2 not configured. Set CLOUDFLARE_R2_* environment variables."
1190
+ },
1182
1191
  { status: 400 }
1183
1192
  );
1184
1193
  }
@@ -1303,15 +1312,20 @@ async function handleSyncStream(request) {
1303
1312
  async start(controller) {
1304
1313
  const sendEvent = (data) => {
1305
1314
  try {
1306
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
1315
+ controller.enqueue(
1316
+ encoder.encode(`data: ${JSON.stringify(data)}
1307
1317
 
1308
- `));
1318
+ `)
1319
+ );
1309
1320
  } catch {
1310
1321
  }
1311
1322
  };
1312
1323
  try {
1313
1324
  if (!accountId || !accessKeyId || !secretAccessKey || !bucketName || !publicUrl) {
1314
- sendEvent({ type: "error", message: "R2 not configured. Set CLOUDFLARE_R2_* environment variables." });
1325
+ sendEvent({
1326
+ type: "error",
1327
+ message: "R2 not configured. Set CLOUDFLARE_R2_* environment variables."
1328
+ });
1315
1329
  controller.close();
1316
1330
  return;
1317
1331
  }
@@ -1360,7 +1374,9 @@ async function handleSyncStream(request) {
1360
1374
  }
1361
1375
  const entry = getMetaEntry(meta, imageKey);
1362
1376
  if (!entry) {
1363
- errors.push(`Image not found in meta: ${imageKey}. Run Scan first.`);
1377
+ errors.push(
1378
+ `Image not found in meta: ${imageKey}. Run Scan first.`
1379
+ );
1364
1380
  sendEvent({
1365
1381
  type: "progress",
1366
1382
  current: i + 1,
@@ -1495,7 +1511,7 @@ async function handleSyncStream(request) {
1495
1511
  headers: {
1496
1512
  "Content-Type": "text/event-stream",
1497
1513
  "Cache-Control": "no-cache",
1498
- "Connection": "keep-alive"
1514
+ Connection: "keep-alive"
1499
1515
  }
1500
1516
  });
1501
1517
  }
@@ -1534,7 +1550,13 @@ async function handleUnprocessStream(request) {
1534
1550
  if (isCancelled()) {
1535
1551
  await saveMeta(meta);
1536
1552
  if (operationId) clearCancelledOperation(operationId);
1537
- sendEvent({ type: "complete", processed: removed.length, errors: errors.length, message: `Stopped. Removed thumbnails for ${removed.length} image${removed.length !== 1 ? "s" : ""}.`, cancelled: true });
1553
+ sendEvent({
1554
+ type: "complete",
1555
+ processed: removed.length,
1556
+ errors: errors.length,
1557
+ message: `Stopped. Removed thumbnails for ${removed.length} image${removed.length !== 1 ? "s" : ""}.`,
1558
+ cancelled: true
1559
+ });
1538
1560
  controller.close();
1539
1561
  return;
1540
1562
  }
@@ -1676,7 +1698,13 @@ async function handleReprocessStream(request) {
1676
1698
  if (isCancelled()) {
1677
1699
  await saveMeta(meta);
1678
1700
  if (operationId) clearCancelledOperation(operationId);
1679
- sendEvent({ type: "complete", processed: processed.length, errors: errors.length, message: `Stopped. Generated thumbnails for ${processed.length} image${processed.length !== 1 ? "s" : ""}.`, cancelled: true });
1701
+ sendEvent({
1702
+ type: "complete",
1703
+ processed: processed.length,
1704
+ errors: errors.length,
1705
+ message: `Stopped. Generated thumbnails for ${processed.length} image${processed.length !== 1 ? "s" : ""}.`,
1706
+ cancelled: true
1707
+ });
1680
1708
  controller.close();
1681
1709
  return;
1682
1710
  }
@@ -1714,7 +1742,10 @@ async function handleReprocessStream(request) {
1714
1742
  const isSvg = ext === ".svg";
1715
1743
  if (isSvg) {
1716
1744
  const imageDir = path6.dirname(imageKey.slice(1));
1717
- const imagesPath = getPublicPath("images", imageDir === "." ? "" : imageDir);
1745
+ const imagesPath = getPublicPath(
1746
+ "images",
1747
+ imageDir === "." ? "" : imageDir
1748
+ );
1718
1749
  await fs6.mkdir(imagesPath, { recursive: true });
1719
1750
  const fileName = path6.basename(imageKey);
1720
1751
  const destPath = path6.join(imagesPath, fileName);
@@ -1805,9 +1836,11 @@ async function handleDownloadStream(request) {
1805
1836
  const encoder = new TextEncoder();
1806
1837
  const sendEvent = (data) => {
1807
1838
  try {
1808
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
1839
+ controller.enqueue(
1840
+ encoder.encode(`data: ${JSON.stringify(data)}
1809
1841
 
1810
- `));
1842
+ `)
1843
+ );
1811
1844
  } catch {
1812
1845
  }
1813
1846
  };
@@ -1920,7 +1953,7 @@ async function handleDownloadStream(request) {
1920
1953
  headers: {
1921
1954
  "Content-Type": "text/event-stream",
1922
1955
  "Cache-Control": "no-cache",
1923
- "Connection": "keep-alive"
1956
+ Connection: "keep-alive"
1924
1957
  }
1925
1958
  });
1926
1959
  }
@@ -1944,8 +1977,8 @@ async function handlePushUpdatesStream(request) {
1944
1977
  controller.close();
1945
1978
  return;
1946
1979
  }
1947
- const { paths, operationId } = await request.json();
1948
- if (!paths || !Array.isArray(paths) || paths.length === 0) {
1980
+ const { paths: inputPaths, operationId } = await request.json();
1981
+ if (!inputPaths || !Array.isArray(inputPaths) || inputPaths.length === 0) {
1949
1982
  sendEvent({ type: "error", message: "No paths provided" });
1950
1983
  controller.close();
1951
1984
  return;
@@ -1959,16 +1992,45 @@ async function handlePushUpdatesStream(request) {
1959
1992
  const meta = await loadMeta();
1960
1993
  const cdnUrls = getCdnUrls(meta);
1961
1994
  const r2PublicUrl = publicUrl.replace(/\/$/, "");
1995
+ const paths = [];
1996
+ for (const inputPath of inputPaths) {
1997
+ const key = inputPath.startsWith("public/") ? "/" + inputPath.slice(7) : inputPath;
1998
+ const isFolder = !key.match(/\.[a-zA-Z0-9]+$/);
1999
+ if (isFolder) {
2000
+ const folderPrefix = key.endsWith("/") ? key : key + "/";
2001
+ for (const [metaKey, entry] of Object.entries(meta)) {
2002
+ if (metaKey.startsWith(folderPrefix) && entry && typeof entry === "object" && "u" in entry && entry.u === 1) {
2003
+ paths.push(metaKey);
2004
+ }
2005
+ }
2006
+ } else {
2007
+ paths.push(inputPath);
2008
+ }
2009
+ }
1962
2010
  const pushed = [];
1963
2011
  const skipped = [];
1964
2012
  const errors = [];
1965
2013
  const total = paths.length;
2014
+ if (total === 0) {
2015
+ sendEvent({
2016
+ type: "complete",
2017
+ pushed: 0,
2018
+ message: "No files with pending updates found."
2019
+ });
2020
+ controller.close();
2021
+ return;
2022
+ }
1966
2023
  sendEvent({ type: "start", total });
1967
2024
  for (let i = 0; i < paths.length; i++) {
1968
2025
  if (isCancelled()) {
1969
2026
  await saveMeta(meta);
1970
2027
  if (operationId) clearCancelledOperation(operationId);
1971
- sendEvent({ type: "complete", pushed: pushed.length, message: `Stopped. ${pushed.length} file${pushed.length !== 1 ? "s" : ""} pushed.`, cancelled: true });
2028
+ sendEvent({
2029
+ type: "complete",
2030
+ pushed: pushed.length,
2031
+ message: `Stopped. ${pushed.length} file${pushed.length !== 1 ? "s" : ""} pushed.`,
2032
+ cancelled: true
2033
+ });
1972
2034
  controller.close();
1973
2035
  return;
1974
2036
  }
@@ -2006,18 +2068,22 @@ async function handlePushUpdatesStream(request) {
2006
2068
  const contentType = getContentType(path6.basename(key));
2007
2069
  const uploadKey = key.startsWith("/") ? key.slice(1) : key;
2008
2070
  try {
2009
- await s3.send(new DeleteObjectCommand2({
2010
- Bucket: bucketName,
2011
- Key: uploadKey
2012
- }));
2071
+ await s3.send(
2072
+ new DeleteObjectCommand2({
2073
+ Bucket: bucketName,
2074
+ Key: uploadKey
2075
+ })
2076
+ );
2013
2077
  } catch {
2014
2078
  }
2015
- await s3.send(new PutObjectCommand2({
2016
- Bucket: bucketName,
2017
- Key: uploadKey,
2018
- Body: buffer,
2019
- ContentType: contentType
2020
- }));
2079
+ await s3.send(
2080
+ new PutObjectCommand2({
2081
+ Bucket: bucketName,
2082
+ Key: uploadKey,
2083
+ Body: buffer,
2084
+ ContentType: contentType
2085
+ })
2086
+ );
2021
2087
  if (isProcessed(entry)) {
2022
2088
  await deleteThumbnailsFromCdn(key);
2023
2089
  const processedEntry = await processImage(buffer, key);
@@ -2083,7 +2149,7 @@ async function handlePushUpdatesStream(request) {
2083
2149
  headers: {
2084
2150
  "Content-Type": "text/event-stream",
2085
2151
  "Cache-Control": "no-cache",
2086
- "Connection": "keep-alive"
2152
+ Connection: "keep-alive"
2087
2153
  }
2088
2154
  });
2089
2155
  }
@@ -2091,22 +2157,43 @@ async function handleCancelStreamOperation(request) {
2091
2157
  try {
2092
2158
  const { operationId } = await request.json();
2093
2159
  if (!operationId || typeof operationId !== "string") {
2094
- return jsonResponse({ error: "No operation ID provided" }, { status: 400 });
2160
+ return jsonResponse(
2161
+ { error: "No operation ID provided" },
2162
+ { status: 400 }
2163
+ );
2095
2164
  }
2096
2165
  cancelOperation(operationId);
2097
2166
  return jsonResponse({ success: true, operationId });
2098
2167
  } catch (error) {
2099
2168
  console.error("Failed to cancel operation:", error);
2100
- return jsonResponse({ error: "Failed to cancel operation" }, { status: 500 });
2169
+ return jsonResponse(
2170
+ { error: "Failed to cancel operation" },
2171
+ { status: 500 }
2172
+ );
2101
2173
  }
2102
2174
  }
2103
2175
  async function handleCancelUpdates(request) {
2104
2176
  try {
2105
- const { paths } = await request.json();
2106
- if (!paths || !Array.isArray(paths) || paths.length === 0) {
2177
+ const { paths: inputPaths } = await request.json();
2178
+ if (!inputPaths || !Array.isArray(inputPaths) || inputPaths.length === 0) {
2107
2179
  return jsonResponse({ error: "No paths provided" }, { status: 400 });
2108
2180
  }
2109
2181
  const meta = await loadMeta();
2182
+ const paths = [];
2183
+ for (const inputPath of inputPaths) {
2184
+ const key = inputPath.startsWith("public/") ? "/" + inputPath.slice(7) : inputPath;
2185
+ const isFolder = !key.match(/\.[a-zA-Z0-9]+$/);
2186
+ if (isFolder) {
2187
+ const folderPrefix = key.endsWith("/") ? key : key + "/";
2188
+ for (const [metaKey, entry] of Object.entries(meta)) {
2189
+ if (metaKey.startsWith(folderPrefix) && entry && typeof entry === "object" && "u" in entry && entry.u === 1) {
2190
+ paths.push(metaKey);
2191
+ }
2192
+ }
2193
+ } else {
2194
+ paths.push(inputPath);
2195
+ }
2196
+ }
2110
2197
  const cancelled = [];
2111
2198
  const skipped = [];
2112
2199
  const errors = [];
@@ -2169,7 +2256,9 @@ async function handleUpload(request) {
2169
2256
  }
2170
2257
  if (relativeDir === "images" || relativeDir.startsWith("images/")) {
2171
2258
  return jsonResponse(
2172
- { error: "Cannot upload to images/ folder. Upload to public/ instead - thumbnails are generated automatically." },
2259
+ {
2260
+ error: "Cannot upload to images/ folder. Upload to public/ instead - thumbnails are generated automatically."
2261
+ },
2173
2262
  { status: 400 }
2174
2263
  );
2175
2264
  }
@@ -2199,7 +2288,8 @@ async function handleUpload(request) {
2199
2288
  }
2200
2289
  if (isImage && ext !== ".svg") {
2201
2290
  try {
2202
- const metadata = await sharp2(buffer).metadata();
2291
+ const rotatedBuffer = await sharp2(buffer).rotate().toBuffer();
2292
+ const metadata = await sharp2(rotatedBuffer).metadata();
2203
2293
  meta[imageKey] = {
2204
2294
  o: { w: metadata.width || 0, h: metadata.height || 0 }
2205
2295
  };
@@ -2218,7 +2308,10 @@ async function handleUpload(request) {
2218
2308
  } catch (error) {
2219
2309
  console.error("Failed to upload:", error);
2220
2310
  const message = error instanceof Error ? error.message : "Unknown error";
2221
- return jsonResponse({ error: `Failed to upload file: ${message}` }, { status: 500 });
2311
+ return jsonResponse(
2312
+ { error: `Failed to upload file: ${message}` },
2313
+ { status: 500 }
2314
+ );
2222
2315
  }
2223
2316
  }
2224
2317
  async function handleDelete(request) {
@@ -2348,9 +2441,11 @@ async function handleDeleteStream(request) {
2348
2441
  async start(controller) {
2349
2442
  const sendEvent = (data) => {
2350
2443
  try {
2351
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
2444
+ controller.enqueue(
2445
+ encoder.encode(`data: ${JSON.stringify(data)}
2352
2446
 
2353
- `));
2447
+ `)
2448
+ );
2354
2449
  } catch {
2355
2450
  }
2356
2451
  };
@@ -2529,7 +2624,7 @@ async function handleDeleteStream(request) {
2529
2624
  headers: {
2530
2625
  "Content-Type": "text/event-stream",
2531
2626
  "Cache-Control": "no-cache",
2532
- "Connection": "keep-alive"
2627
+ Connection: "keep-alive"
2533
2628
  }
2534
2629
  });
2535
2630
  }
@@ -2537,7 +2632,10 @@ async function handleCreateFolder(request) {
2537
2632
  try {
2538
2633
  const { parentPath, name } = await request.json();
2539
2634
  if (!name || typeof name !== "string") {
2540
- return jsonResponse({ error: "Folder name is required" }, { status: 400 });
2635
+ return jsonResponse(
2636
+ { error: "Folder name is required" },
2637
+ { status: 400 }
2638
+ );
2541
2639
  }
2542
2640
  const sanitizedName = slugifyFolderName(name);
2543
2641
  if (!sanitizedName) {
@@ -2550,11 +2648,17 @@ async function handleCreateFolder(request) {
2550
2648
  }
2551
2649
  try {
2552
2650
  await fs7.access(folderPath);
2553
- return jsonResponse({ error: "A folder with this name already exists" }, { status: 400 });
2651
+ return jsonResponse(
2652
+ { error: "A folder with this name already exists" },
2653
+ { status: 400 }
2654
+ );
2554
2655
  } catch {
2555
2656
  }
2556
2657
  await fs7.mkdir(folderPath, { recursive: true });
2557
- return jsonResponse({ success: true, path: path7.join(safePath, sanitizedName) });
2658
+ return jsonResponse({
2659
+ success: true,
2660
+ path: path7.join(safePath, sanitizedName)
2661
+ });
2558
2662
  } catch (error) {
2559
2663
  console.error("Failed to create folder:", error);
2560
2664
  return jsonResponse({ error: "Failed to create folder" }, { status: 500 });
@@ -2565,7 +2669,10 @@ async function handleRename(request) {
2565
2669
  try {
2566
2670
  const { oldPath, newName } = await request.json();
2567
2671
  if (!oldPath || !newName) {
2568
- return jsonResponse({ error: "Path and new name are required" }, { status: 400 });
2672
+ return jsonResponse(
2673
+ { error: "Path and new name are required" },
2674
+ { status: 400 }
2675
+ );
2569
2676
  }
2570
2677
  const safePath = oldPath.replace(/\.\./g, "");
2571
2678
  const absoluteOldPath = getWorkspacePath(safePath);
@@ -2590,7 +2697,10 @@ async function handleRename(request) {
2590
2697
  isFile = stats.isFile();
2591
2698
  } catch {
2592
2699
  if (!isInCloud) {
2593
- return jsonResponse({ error: "File or folder not found" }, { status: 404 });
2700
+ return jsonResponse(
2701
+ { error: "File or folder not found" },
2702
+ { status: 404 }
2703
+ );
2594
2704
  }
2595
2705
  }
2596
2706
  const sanitizedName = isFile ? slugifyFilename(newName) : slugifyFolderName(newName);
@@ -2599,14 +2709,23 @@ async function handleRename(request) {
2599
2709
  }
2600
2710
  const parentDir = path7.dirname(absoluteOldPath);
2601
2711
  const absoluteNewPath = path7.join(parentDir, sanitizedName);
2602
- const newRelativePath = path7.join(path7.dirname(oldRelativePath), sanitizedName);
2712
+ const newRelativePath = path7.join(
2713
+ path7.dirname(oldRelativePath),
2714
+ sanitizedName
2715
+ );
2603
2716
  const newKey = "/" + newRelativePath;
2604
2717
  if (meta[newKey]) {
2605
- return jsonResponse({ error: "An item with this name already exists" }, { status: 400 });
2718
+ return jsonResponse(
2719
+ { error: "An item with this name already exists" },
2720
+ { status: 400 }
2721
+ );
2606
2722
  }
2607
2723
  try {
2608
2724
  await fs7.access(absoluteNewPath);
2609
- return jsonResponse({ error: "An item with this name already exists" }, { status: 400 });
2725
+ return jsonResponse(
2726
+ { error: "An item with this name already exists" },
2727
+ { status: 400 }
2728
+ );
2610
2729
  } catch {
2611
2730
  }
2612
2731
  if (isInOurR2 && !hasLocalFile) {
@@ -2664,7 +2783,10 @@ async function handleRenameStream(request) {
2664
2783
  try {
2665
2784
  const { oldPath, newName, operationId } = await request.json();
2666
2785
  if (!oldPath || !newName) {
2667
- sendEvent({ type: "error", message: "Path and new name are required" });
2786
+ sendEvent({
2787
+ type: "error",
2788
+ message: "Path and new name are required"
2789
+ });
2668
2790
  controller.close();
2669
2791
  return;
2670
2792
  }
@@ -2693,7 +2815,9 @@ async function handleRenameStream(request) {
2693
2815
  isFile = true;
2694
2816
  } else {
2695
2817
  const folderPrefix = oldKey2 + "/";
2696
- const hasChildrenInMeta = Object.keys(meta2).some((key) => key.startsWith(folderPrefix));
2818
+ const hasChildrenInMeta = Object.keys(meta2).some(
2819
+ (key) => key.startsWith(folderPrefix)
2820
+ );
2697
2821
  if (hasChildrenInMeta) {
2698
2822
  isFile = false;
2699
2823
  isVirtualFolder = true;
@@ -2712,14 +2836,20 @@ async function handleRenameStream(request) {
2712
2836
  }
2713
2837
  const parentDir = path7.dirname(absoluteOldPath);
2714
2838
  const absoluteNewPath = path7.join(parentDir, sanitizedName);
2715
- const newRelativePath = path7.join(path7.dirname(oldRelativePath), sanitizedName);
2839
+ const newRelativePath = path7.join(
2840
+ path7.dirname(oldRelativePath),
2841
+ sanitizedName
2842
+ );
2716
2843
  const newPath = path7.join(path7.dirname(safePath), sanitizedName);
2717
2844
  const meta = await loadMeta();
2718
2845
  const cdnUrls = getCdnUrls(meta);
2719
2846
  if (isFile) {
2720
2847
  const newKey2 = "/" + newRelativePath;
2721
2848
  if (meta[newKey2]) {
2722
- sendEvent({ type: "error", message: "An item with this name already exists" });
2849
+ sendEvent({
2850
+ type: "error",
2851
+ message: "An item with this name already exists"
2852
+ });
2723
2853
  controller.close();
2724
2854
  return;
2725
2855
  }
@@ -2727,7 +2857,10 @@ async function handleRenameStream(request) {
2727
2857
  if (!isVirtualFolder) {
2728
2858
  try {
2729
2859
  await fs7.access(absoluteNewPath);
2730
- sendEvent({ type: "error", message: "An item with this name already exists" });
2860
+ sendEvent({
2861
+ type: "error",
2862
+ message: "An item with this name already exists"
2863
+ });
2731
2864
  controller.close();
2732
2865
  return;
2733
2866
  } catch {
@@ -2735,9 +2868,14 @@ async function handleRenameStream(request) {
2735
2868
  }
2736
2869
  if (isVirtualFolder) {
2737
2870
  const newPrefix = "/" + newRelativePath + "/";
2738
- const hasConflict = Object.keys(meta).some((key) => key.startsWith(newPrefix));
2871
+ const hasConflict = Object.keys(meta).some(
2872
+ (key) => key.startsWith(newPrefix)
2873
+ );
2739
2874
  if (hasConflict) {
2740
- sendEvent({ type: "error", message: "A folder with this name already exists" });
2875
+ sendEvent({
2876
+ type: "error",
2877
+ message: "A folder with this name already exists"
2878
+ });
2741
2879
  controller.close();
2742
2880
  return;
2743
2881
  }
@@ -2749,11 +2887,19 @@ async function handleRenameStream(request) {
2749
2887
  for (const [key, entry2] of Object.entries(meta)) {
2750
2888
  if (key.startsWith(oldPrefix) && entry2 && typeof entry2 === "object") {
2751
2889
  const newKey2 = key.replace(oldPrefix, newPrefix);
2752
- itemsToUpdate.push({ oldKey: key, newKey: newKey2, entry: entry2 });
2890
+ itemsToUpdate.push({
2891
+ oldKey: key,
2892
+ newKey: newKey2,
2893
+ entry: entry2
2894
+ });
2753
2895
  }
2754
2896
  }
2755
2897
  const total = itemsToUpdate.length + 1;
2756
- sendEvent({ type: "start", total, message: `Renaming folder with ${itemsToUpdate.length} item(s)...` });
2898
+ sendEvent({
2899
+ type: "start",
2900
+ total,
2901
+ message: `Renaming folder with ${itemsToUpdate.length} item(s)...`
2902
+ });
2757
2903
  if (hasLocalItem) {
2758
2904
  await fs7.rename(absoluteOldPath, absoluteNewPath);
2759
2905
  const imagesDir = getPublicPath("/images");
@@ -2766,12 +2912,21 @@ async function handleRenameStream(request) {
2766
2912
  } catch {
2767
2913
  }
2768
2914
  }
2769
- sendEvent({ type: "progress", current: 1, total, renamed: 1, message: "Renamed folder" });
2915
+ sendEvent({
2916
+ type: "progress",
2917
+ current: 1,
2918
+ total,
2919
+ renamed: 1,
2920
+ message: "Renamed folder"
2921
+ });
2770
2922
  let renamed = 1;
2771
2923
  const handleRenameCancel = async () => {
2772
2924
  await saveMeta(meta);
2773
2925
  await deleteEmptyFolders(absoluteOldPath);
2774
- const oldThumbFolder2 = path7.join(getPublicPath("/images"), oldRelativePath);
2926
+ const oldThumbFolder2 = path7.join(
2927
+ getPublicPath("/images"),
2928
+ oldRelativePath
2929
+ );
2775
2930
  await deleteEmptyFolders(oldThumbFolder2);
2776
2931
  sendEvent({ type: "complete", renamed, newPath, cancelled: true });
2777
2932
  controller.close();
@@ -2814,7 +2969,10 @@ async function handleRenameStream(request) {
2814
2969
  });
2815
2970
  }
2816
2971
  await deleteEmptyFolders(absoluteOldPath);
2817
- const oldThumbFolder = path7.join(getPublicPath("/images"), oldRelativePath);
2972
+ const oldThumbFolder = path7.join(
2973
+ getPublicPath("/images"),
2974
+ oldRelativePath
2975
+ );
2818
2976
  await deleteEmptyFolders(oldThumbFolder);
2819
2977
  sendEvent({ type: "complete", renamed, newPath });
2820
2978
  controller.close();
@@ -2937,7 +3095,9 @@ async function handleMoveStream(request) {
2937
3095
  const entries = await fs7.readdir(dir, { withFileTypes: true });
2938
3096
  for (const entry of entries) {
2939
3097
  if (entry.isDirectory()) {
2940
- count += await countFilesRecursive(path7.join(dir, entry.name));
3098
+ count += await countFilesRecursive(
3099
+ path7.join(dir, entry.name)
3100
+ );
2941
3101
  } else {
2942
3102
  count++;
2943
3103
  }
@@ -2959,7 +3119,15 @@ async function handleMoveStream(request) {
2959
3119
  }
2960
3120
  }
2961
3121
  totalFiles += localFileCount + cloudOnlyCount;
2962
- expandedItems.push({ itemPath, safePath, itemName, oldKey, newKey, newAbsolutePath, isVirtualFolder: false });
3122
+ expandedItems.push({
3123
+ itemPath,
3124
+ safePath,
3125
+ itemName,
3126
+ oldKey,
3127
+ newKey,
3128
+ newAbsolutePath,
3129
+ isVirtualFolder: false
3130
+ });
2963
3131
  } else if (!hasLocalItem) {
2964
3132
  const folderPrefix = oldKey + "/";
2965
3133
  const virtualItems = [];
@@ -2967,20 +3135,49 @@ async function handleMoveStream(request) {
2967
3135
  if (key.startsWith(folderPrefix) && metaEntry && typeof metaEntry === "object") {
2968
3136
  const relativePath = key.slice(folderPrefix.length);
2969
3137
  const destNewKey = newKey + "/" + relativePath;
2970
- virtualItems.push({ oldKey: key, newKey: destNewKey, entry: metaEntry });
3138
+ virtualItems.push({
3139
+ oldKey: key,
3140
+ newKey: destNewKey,
3141
+ entry: metaEntry
3142
+ });
2971
3143
  }
2972
3144
  }
2973
3145
  if (virtualItems.length > 0) {
2974
3146
  totalFiles += virtualItems.length;
2975
- expandedItems.push({ itemPath, safePath, itemName, oldKey, newKey, newAbsolutePath, isVirtualFolder: true, virtualFolderItems: virtualItems });
3147
+ expandedItems.push({
3148
+ itemPath,
3149
+ safePath,
3150
+ itemName,
3151
+ oldKey,
3152
+ newKey,
3153
+ newAbsolutePath,
3154
+ isVirtualFolder: true,
3155
+ virtualFolderItems: virtualItems
3156
+ });
2976
3157
  sourceFolders.add(absolutePath);
2977
3158
  } else {
2978
3159
  totalFiles++;
2979
- expandedItems.push({ itemPath, safePath, itemName, oldKey, newKey, newAbsolutePath, isVirtualFolder: false });
3160
+ expandedItems.push({
3161
+ itemPath,
3162
+ safePath,
3163
+ itemName,
3164
+ oldKey,
3165
+ newKey,
3166
+ newAbsolutePath,
3167
+ isVirtualFolder: false
3168
+ });
2980
3169
  }
2981
3170
  } else {
2982
3171
  totalFiles++;
2983
- expandedItems.push({ itemPath, safePath, itemName, oldKey, newKey, newAbsolutePath, isVirtualFolder: false });
3172
+ expandedItems.push({
3173
+ itemPath,
3174
+ safePath,
3175
+ itemName,
3176
+ oldKey,
3177
+ newKey,
3178
+ newAbsolutePath,
3179
+ isVirtualFolder: false
3180
+ });
2984
3181
  }
2985
3182
  }
2986
3183
  sendEvent({ type: "start", total: totalFiles });
@@ -2992,7 +3189,13 @@ async function handleMoveStream(request) {
2992
3189
  await deleteEmptyFolders(folder);
2993
3190
  }
2994
3191
  await deleteEmptyFolders(absoluteDestination);
2995
- sendEvent({ type: "complete", moved: filesMoved, errors: errors.length, errorMessages: errors, cancelled: true });
3192
+ sendEvent({
3193
+ type: "complete",
3194
+ moved: filesMoved,
3195
+ errors: errors.length,
3196
+ errorMessages: errors,
3197
+ cancelled: true
3198
+ });
2996
3199
  controller.close();
2997
3200
  };
2998
3201
  for (const expandedItem of expandedItems) {
@@ -3000,7 +3203,16 @@ async function handleMoveStream(request) {
3000
3203
  await handleCancel();
3001
3204
  return;
3002
3205
  }
3003
- const { itemPath, safePath, itemName, oldKey, newKey, newAbsolutePath, isVirtualFolder, virtualFolderItems } = expandedItem;
3206
+ const {
3207
+ itemPath,
3208
+ safePath,
3209
+ itemName,
3210
+ oldKey,
3211
+ newKey,
3212
+ newAbsolutePath,
3213
+ isVirtualFolder,
3214
+ virtualFolderItems
3215
+ } = expandedItem;
3004
3216
  if (isVirtualFolder && virtualFolderItems) {
3005
3217
  for (const vItem of virtualFolderItems) {
3006
3218
  if (isCancelled()) {
@@ -3015,11 +3227,18 @@ async function handleMoveStream(request) {
3015
3227
  let vItemMoved = false;
3016
3228
  if (isItemInR2) {
3017
3229
  try {
3018
- await moveInCdn(vItem.oldKey, vItem.newKey, itemHasThumbnails);
3230
+ await moveInCdn(
3231
+ vItem.oldKey,
3232
+ vItem.newKey,
3233
+ itemHasThumbnails
3234
+ );
3019
3235
  vItemMoved = true;
3020
3236
  filesMoved++;
3021
3237
  } catch (err) {
3022
- console.error(`Failed to move cloud item ${vItem.oldKey}:`, err);
3238
+ console.error(
3239
+ `Failed to move cloud item ${vItem.oldKey}:`,
3240
+ err
3241
+ );
3023
3242
  delete meta[vItem.oldKey];
3024
3243
  await saveMeta(meta);
3025
3244
  }
@@ -3043,7 +3262,10 @@ async function handleMoveStream(request) {
3043
3262
  }
3044
3263
  const newFolderPath = getPublicPath(newKey);
3045
3264
  await deleteEmptyFolders(newFolderPath);
3046
- const newThumbFolder = path7.join(getPublicPath("images"), newKey.slice(1));
3265
+ const newThumbFolder = path7.join(
3266
+ getPublicPath("images"),
3267
+ newKey.slice(1)
3268
+ );
3047
3269
  await deleteEmptyFolders(newThumbFolder);
3048
3270
  const oldFolderPath = getPublicPath(oldKey);
3049
3271
  sourceFolders.add(oldFolderPath);
@@ -3076,7 +3298,9 @@ async function handleMoveStream(request) {
3076
3298
  if (isRemote) {
3077
3299
  const remoteUrl = `${fileCdnUrl}${oldKey}`;
3078
3300
  const buffer = await downloadFromRemoteUrl(remoteUrl);
3079
- await fs7.mkdir(path7.dirname(newAbsolutePath), { recursive: true });
3301
+ await fs7.mkdir(path7.dirname(newAbsolutePath), {
3302
+ recursive: true
3303
+ });
3080
3304
  await fs7.writeFile(newAbsolutePath, buffer);
3081
3305
  const newEntry = {
3082
3306
  o: entry?.o,
@@ -3161,7 +3385,9 @@ async function handleMoveStream(request) {
3161
3385
  }
3162
3386
  const stats = await fs7.stat(absolutePath);
3163
3387
  if (stats.isFile()) {
3164
- await fs7.mkdir(path7.dirname(newAbsolutePath), { recursive: true });
3388
+ await fs7.mkdir(path7.dirname(newAbsolutePath), {
3389
+ recursive: true
3390
+ });
3165
3391
  await fs7.rename(absolutePath, newAbsolutePath);
3166
3392
  if (isImage && entry) {
3167
3393
  const oldThumbPaths = getAllThumbnailPaths(oldKey);
@@ -3172,7 +3398,9 @@ async function handleMoveStream(request) {
3172
3398
  try {
3173
3399
  await fs7.access(oldThumbPath);
3174
3400
  sourceFolders.add(path7.dirname(oldThumbPath));
3175
- await fs7.mkdir(path7.dirname(newThumbPath), { recursive: true });
3401
+ await fs7.mkdir(path7.dirname(newThumbPath), {
3402
+ recursive: true
3403
+ });
3176
3404
  await fs7.rename(oldThumbPath, newThumbPath);
3177
3405
  } catch {
3178
3406
  }
@@ -3208,13 +3436,21 @@ async function handleMoveStream(request) {
3208
3436
  const newPrefix = newKey + "/";
3209
3437
  const localFiles = [];
3210
3438
  const collectLocalFiles = async (dir, relativeDir) => {
3211
- const entries = await fs7.readdir(dir, { withFileTypes: true });
3439
+ const entries = await fs7.readdir(dir, {
3440
+ withFileTypes: true
3441
+ });
3212
3442
  for (const dirEntry of entries) {
3213
3443
  const entryRelPath = relativeDir ? `${relativeDir}/${dirEntry.name}` : dirEntry.name;
3214
3444
  if (dirEntry.isDirectory()) {
3215
- await collectLocalFiles(path7.join(dir, dirEntry.name), entryRelPath);
3445
+ await collectLocalFiles(
3446
+ path7.join(dir, dirEntry.name),
3447
+ entryRelPath
3448
+ );
3216
3449
  } else {
3217
- localFiles.push({ relativePath: entryRelPath, isImage: isImageFile(dirEntry.name) });
3450
+ localFiles.push({
3451
+ relativePath: entryRelPath,
3452
+ isImage: isImageFile(dirEntry.name)
3453
+ });
3218
3454
  }
3219
3455
  }
3220
3456
  };
@@ -3240,13 +3476,21 @@ async function handleMoveStream(request) {
3240
3476
  await handleCancel();
3241
3477
  return;
3242
3478
  }
3243
- const fileOldPath = path7.join(absolutePath, localFile.relativePath);
3244
- const fileNewPath = path7.join(newAbsolutePath, localFile.relativePath);
3479
+ const fileOldPath = path7.join(
3480
+ absolutePath,
3481
+ localFile.relativePath
3482
+ );
3483
+ const fileNewPath = path7.join(
3484
+ newAbsolutePath,
3485
+ localFile.relativePath
3486
+ );
3245
3487
  const fileOldKey = oldPrefix + localFile.relativePath;
3246
3488
  const fileNewKey = newPrefix + localFile.relativePath;
3247
3489
  const fileEntry = meta[fileOldKey];
3248
3490
  sourceFolders.add(path7.dirname(fileOldPath));
3249
- await fs7.mkdir(path7.dirname(fileNewPath), { recursive: true });
3491
+ await fs7.mkdir(path7.dirname(fileNewPath), {
3492
+ recursive: true
3493
+ });
3250
3494
  await fs7.rename(fileOldPath, fileNewPath);
3251
3495
  filesMoved++;
3252
3496
  if (localFile.isImage && fileEntry) {
@@ -3258,7 +3502,9 @@ async function handleMoveStream(request) {
3258
3502
  try {
3259
3503
  await fs7.access(oldThumbPath);
3260
3504
  sourceFolders.add(path7.dirname(oldThumbPath));
3261
- await fs7.mkdir(path7.dirname(newThumbPath), { recursive: true });
3505
+ await fs7.mkdir(path7.dirname(newThumbPath), {
3506
+ recursive: true
3507
+ });
3262
3508
  await fs7.rename(oldThumbPath, newThumbPath);
3263
3509
  } catch {
3264
3510
  }
@@ -3297,11 +3543,18 @@ async function handleMoveStream(request) {
3297
3543
  let cloudFileMoved = false;
3298
3544
  if (cloudIsInR2) {
3299
3545
  try {
3300
- await moveInCdn(cloudFile.oldKey, cloudFile.newKey, cloudHasThumbs);
3546
+ await moveInCdn(
3547
+ cloudFile.oldKey,
3548
+ cloudFile.newKey,
3549
+ cloudHasThumbs
3550
+ );
3301
3551
  cloudFileMoved = true;
3302
3552
  filesMoved++;
3303
3553
  } catch (err) {
3304
- console.error(`Failed to move cloud file ${cloudFile.oldKey}:`, err);
3554
+ console.error(
3555
+ `Failed to move cloud file ${cloudFile.oldKey}:`,
3556
+ err
3557
+ );
3305
3558
  delete meta[cloudFile.oldKey];
3306
3559
  await saveMeta(meta);
3307
3560
  }
@@ -3323,7 +3576,10 @@ async function handleMoveStream(request) {
3323
3576
  }
3324
3577
  sourceFolders.add(absolutePath);
3325
3578
  const oldThumbRelPath = oldKey.slice(1);
3326
- const oldThumbFolder = path7.join(getPublicPath("images"), oldThumbRelPath);
3579
+ const oldThumbFolder = path7.join(
3580
+ getPublicPath("images"),
3581
+ oldThumbRelPath
3582
+ );
3327
3583
  sourceFolders.add(oldThumbFolder);
3328
3584
  moved.push(itemPath);
3329
3585
  }
@@ -3365,7 +3621,7 @@ async function handleMoveStream(request) {
3365
3621
  headers: {
3366
3622
  "Content-Type": "text/event-stream",
3367
3623
  "Cache-Control": "no-cache",
3368
- "Connection": "keep-alive"
3624
+ Connection: "keep-alive"
3369
3625
  }
3370
3626
  });
3371
3627
  }
@@ -3385,7 +3641,9 @@ async function handleScanStream() {
3385
3641
  };
3386
3642
  try {
3387
3643
  const meta = await loadMeta();
3388
- const existingCount = Object.keys(meta).filter((k) => !k.startsWith("_")).length;
3644
+ const existingCount = Object.keys(meta).filter(
3645
+ (k) => !k.startsWith("_")
3646
+ ).length;
3389
3647
  const existingKeys = new Set(Object.keys(meta));
3390
3648
  const added = [];
3391
3649
  const renamed = [];
@@ -3400,7 +3658,8 @@ async function handleScanStream() {
3400
3658
  if (entry.name.startsWith(".")) continue;
3401
3659
  const fullPath = path8.join(dir, entry.name);
3402
3660
  const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
3403
- if (relPath === "images" || relPath.startsWith("images/")) continue;
3661
+ if (relPath === "images" || relPath.startsWith("images/"))
3662
+ continue;
3404
3663
  if (entry.isDirectory()) {
3405
3664
  await scanDir(fullPath, relPath);
3406
3665
  } else if (isMediaFile(entry.name)) {
@@ -3484,7 +3743,8 @@ async function handleScanStream() {
3484
3743
  } else {
3485
3744
  try {
3486
3745
  const buffer = await fs8.readFile(fullPath);
3487
- const metadata = await sharp3(buffer).metadata();
3746
+ const rotatedBuffer = await sharp3(buffer).rotate().toBuffer();
3747
+ const metadata = await sharp3(rotatedBuffer).metadata();
3488
3748
  meta[imageKey] = {
3489
3749
  o: { w: metadata.width || 0, h: metadata.height || 0 }
3490
3750
  };
@@ -3505,7 +3765,10 @@ async function handleScanStream() {
3505
3765
  errors.push(relativePath);
3506
3766
  }
3507
3767
  }
3508
- sendEvent({ type: "cleanup", message: "Checking for orphaned thumbnails..." });
3768
+ sendEvent({
3769
+ type: "cleanup",
3770
+ message: "Checking for orphaned thumbnails..."
3771
+ });
3509
3772
  const expectedThumbnails = /* @__PURE__ */ new Set();
3510
3773
  const fileEntries = getFileEntries(meta);
3511
3774
  for (const [imageKey, entry] of fileEntries) {
@@ -3552,7 +3815,9 @@ async function handleScanStream() {
3552
3815
  await cleanEmptyFolders(fullPath);
3553
3816
  try {
3554
3817
  const subEntries = await fs8.readdir(fullPath);
3555
- const meaningfulEntries = subEntries.filter((e) => !e.startsWith("."));
3818
+ const meaningfulEntries = subEntries.filter(
3819
+ (e) => !e.startsWith(".")
3820
+ );
3556
3821
  if (meaningfulEntries.length === 0) {
3557
3822
  await fs8.rm(fullPath, { recursive: true });
3558
3823
  emptyFoldersDeleted++;
@@ -3570,7 +3835,9 @@ async function handleScanStream() {
3570
3835
  let isEmpty = true;
3571
3836
  for (const entry of entries) {
3572
3837
  if (entry.isDirectory()) {
3573
- const subDirEmpty = await cleanImagesEmptyFolders(path8.join(dir, entry.name));
3838
+ const subDirEmpty = await cleanImagesEmptyFolders(
3839
+ path8.join(dir, entry.name)
3840
+ );
3574
3841
  if (!subDirEmpty) isEmpty = false;
3575
3842
  } else if (!entry.name.startsWith(".")) {
3576
3843
  isEmpty = false;
@@ -3589,7 +3856,10 @@ async function handleScanStream() {
3589
3856
  await cleanImagesEmptyFolders(imagesDir);
3590
3857
  } catch {
3591
3858
  }
3592
- sendEvent({ type: "cleanup", message: "Checking for orphaned entries..." });
3859
+ sendEvent({
3860
+ type: "cleanup",
3861
+ message: "Checking for orphaned entries..."
3862
+ });
3593
3863
  const orphanedEntries = [];
3594
3864
  const cdnUrls = meta._cdns || [];
3595
3865
  const r2PublicUrl = (process.env.CLOUDFLARE_R2_PUBLIC_URL || "").replace(/\/$/, "");
@@ -3617,7 +3887,10 @@ async function handleScanStream() {
3617
3887
  }
3618
3888
  }
3619
3889
  if (orphanedEntries.length > 0) {
3620
- sendEvent({ type: "cleanup", message: `Removed ${orphanedEntries.length} orphaned entries...` });
3890
+ sendEvent({
3891
+ type: "cleanup",
3892
+ message: `Removed ${orphanedEntries.length} orphaned entries...`
3893
+ });
3621
3894
  }
3622
3895
  await saveMeta(meta);
3623
3896
  sendEvent({
@@ -3644,7 +3917,7 @@ async function handleScanStream() {
3644
3917
  headers: {
3645
3918
  "Content-Type": "text/event-stream",
3646
3919
  "Cache-Control": "no-cache",
3647
- "Connection": "keep-alive"
3920
+ Connection: "keep-alive"
3648
3921
  }
3649
3922
  });
3650
3923
  }
@@ -3682,7 +3955,10 @@ async function handleDeleteOrphans(request) {
3682
3955
  });
3683
3956
  } catch (error) {
3684
3957
  console.error("Failed to delete orphans:", error);
3685
- return jsonResponse({ error: "Failed to delete orphaned files" }, { status: 500 });
3958
+ return jsonResponse(
3959
+ { error: "Failed to delete orphaned files" },
3960
+ { status: 500 }
3961
+ );
3686
3962
  }
3687
3963
  }
3688
3964
 
@@ -3700,9 +3976,11 @@ async function processRemoteImage(url) {
3700
3976
  throw new Error(`Failed to fetch: ${response.status}`);
3701
3977
  }
3702
3978
  const buffer = Buffer.from(await response.arrayBuffer());
3703
- const metadata = await sharp4(buffer).metadata();
3979
+ const rotatedBuffer = await sharp4(buffer).rotate().toBuffer();
3980
+ const metadata = await sharp4(rotatedBuffer).metadata();
3704
3981
  return {
3705
3982
  o: { w: metadata.width || 0, h: metadata.height || 0 }
3983
+ // b: blur hash would be generated here if needed
3706
3984
  };
3707
3985
  }
3708
3986
  async function handleImportUrls(request) {
@@ -3711,9 +3989,11 @@ async function handleImportUrls(request) {
3711
3989
  async start(controller) {
3712
3990
  const sendEvent = (data) => {
3713
3991
  try {
3714
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
3992
+ controller.enqueue(
3993
+ encoder.encode(`data: ${JSON.stringify(data)}
3715
3994
 
3716
- `));
3995
+ `)
3996
+ );
3717
3997
  } catch {
3718
3998
  }
3719
3999
  };
@@ -3813,7 +4093,7 @@ async function handleImportUrls(request) {
3813
4093
  headers: {
3814
4094
  "Content-Type": "text/event-stream",
3815
4095
  "Cache-Control": "no-cache",
3816
- "Connection": "keep-alive"
4096
+ Connection: "keep-alive"
3817
4097
  }
3818
4098
  });
3819
4099
  }
@@ -3866,9 +4146,12 @@ async function handleGenerateFavicon(request) {
3866
4146
  }
3867
4147
  const fileName = path9.basename(imagePath).toLowerCase();
3868
4148
  if (fileName !== "favicon.png" && fileName !== "favicon.jpg") {
3869
- return jsonResponse({
3870
- error: "Source file must be named favicon.png or favicon.jpg"
3871
- }, { status: 400 });
4149
+ return jsonResponse(
4150
+ {
4151
+ error: "Source file must be named favicon.png or favicon.jpg"
4152
+ },
4153
+ { status: 400 }
4154
+ );
3872
4155
  }
3873
4156
  const sourcePath = getPublicPath(imagePath.replace(/^\//, ""));
3874
4157
  try {
@@ -3878,17 +4161,24 @@ async function handleGenerateFavicon(request) {
3878
4161
  }
3879
4162
  let metadata;
3880
4163
  try {
3881
- metadata = await sharp5(sourcePath).metadata();
4164
+ const rotatedBuffer = await sharp5(sourcePath).rotate().toBuffer();
4165
+ metadata = await sharp5(rotatedBuffer).metadata();
3882
4166
  } catch {
3883
- return jsonResponse({ error: "Source file is not a valid image" }, { status: 400 });
4167
+ return jsonResponse(
4168
+ { error: "Source file is not a valid image" },
4169
+ { status: 400 }
4170
+ );
3884
4171
  }
3885
4172
  const outputDir = getSrcAppPath();
3886
4173
  try {
3887
4174
  await fs9.access(outputDir);
3888
4175
  } catch {
3889
- return jsonResponse({
3890
- error: "Output directory src/app/ not found"
3891
- }, { status: 500 });
4176
+ return jsonResponse(
4177
+ {
4178
+ error: "Output directory src/app/ not found"
4179
+ },
4180
+ { status: 500 }
4181
+ );
3892
4182
  }
3893
4183
  const stream = new ReadableStream({
3894
4184
  async start(controller) {
@@ -3910,7 +4200,7 @@ async function handleGenerateFavicon(request) {
3910
4200
  const config = FAVICON_CONFIGS[i];
3911
4201
  try {
3912
4202
  const outputPath = path9.join(outputDir, config.name);
3913
- await sharp5(sourcePath).resize(config.size, config.size, {
4203
+ await sharp5(sourcePath).rotate().resize(config.size, config.size, {
3914
4204
  fit: "cover",
3915
4205
  position: "center"
3916
4206
  }).png({ quality: 100 }).toFile(outputPath);