@gallop.software/studio 2.3.86 → 2.3.88

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
  }
@@ -1979,7 +2012,11 @@ async function handlePushUpdatesStream(request) {
1979
2012
  const errors = [];
1980
2013
  const total = paths.length;
1981
2014
  if (total === 0) {
1982
- sendEvent({ type: "complete", pushed: 0, message: "No files with pending updates found." });
2015
+ sendEvent({
2016
+ type: "complete",
2017
+ pushed: 0,
2018
+ message: "No files with pending updates found."
2019
+ });
1983
2020
  controller.close();
1984
2021
  return;
1985
2022
  }
@@ -1988,7 +2025,12 @@ async function handlePushUpdatesStream(request) {
1988
2025
  if (isCancelled()) {
1989
2026
  await saveMeta(meta);
1990
2027
  if (operationId) clearCancelledOperation(operationId);
1991
- 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
+ });
1992
2034
  controller.close();
1993
2035
  return;
1994
2036
  }
@@ -2026,18 +2068,22 @@ async function handlePushUpdatesStream(request) {
2026
2068
  const contentType = getContentType(path6.basename(key));
2027
2069
  const uploadKey = key.startsWith("/") ? key.slice(1) : key;
2028
2070
  try {
2029
- await s3.send(new DeleteObjectCommand2({
2030
- Bucket: bucketName,
2031
- Key: uploadKey
2032
- }));
2071
+ await s3.send(
2072
+ new DeleteObjectCommand2({
2073
+ Bucket: bucketName,
2074
+ Key: uploadKey
2075
+ })
2076
+ );
2033
2077
  } catch {
2034
2078
  }
2035
- await s3.send(new PutObjectCommand2({
2036
- Bucket: bucketName,
2037
- Key: uploadKey,
2038
- Body: buffer,
2039
- ContentType: contentType
2040
- }));
2079
+ await s3.send(
2080
+ new PutObjectCommand2({
2081
+ Bucket: bucketName,
2082
+ Key: uploadKey,
2083
+ Body: buffer,
2084
+ ContentType: contentType
2085
+ })
2086
+ );
2041
2087
  if (isProcessed(entry)) {
2042
2088
  await deleteThumbnailsFromCdn(key);
2043
2089
  const processedEntry = await processImage(buffer, key);
@@ -2103,7 +2149,7 @@ async function handlePushUpdatesStream(request) {
2103
2149
  headers: {
2104
2150
  "Content-Type": "text/event-stream",
2105
2151
  "Cache-Control": "no-cache",
2106
- "Connection": "keep-alive"
2152
+ Connection: "keep-alive"
2107
2153
  }
2108
2154
  });
2109
2155
  }
@@ -2111,13 +2157,19 @@ async function handleCancelStreamOperation(request) {
2111
2157
  try {
2112
2158
  const { operationId } = await request.json();
2113
2159
  if (!operationId || typeof operationId !== "string") {
2114
- return jsonResponse({ error: "No operation ID provided" }, { status: 400 });
2160
+ return jsonResponse(
2161
+ { error: "No operation ID provided" },
2162
+ { status: 400 }
2163
+ );
2115
2164
  }
2116
2165
  cancelOperation(operationId);
2117
2166
  return jsonResponse({ success: true, operationId });
2118
2167
  } catch (error) {
2119
2168
  console.error("Failed to cancel operation:", error);
2120
- return jsonResponse({ error: "Failed to cancel operation" }, { status: 500 });
2169
+ return jsonResponse(
2170
+ { error: "Failed to cancel operation" },
2171
+ { status: 500 }
2172
+ );
2121
2173
  }
2122
2174
  }
2123
2175
  async function handleCancelUpdates(request) {
@@ -2204,7 +2256,9 @@ async function handleUpload(request) {
2204
2256
  }
2205
2257
  if (relativeDir === "images" || relativeDir.startsWith("images/")) {
2206
2258
  return jsonResponse(
2207
- { 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
+ },
2208
2262
  { status: 400 }
2209
2263
  );
2210
2264
  }
@@ -2234,7 +2288,8 @@ async function handleUpload(request) {
2234
2288
  }
2235
2289
  if (isImage && ext !== ".svg") {
2236
2290
  try {
2237
- const metadata = await sharp2(buffer).metadata();
2291
+ const rotatedBuffer = await sharp2(buffer).rotate().toBuffer();
2292
+ const metadata = await sharp2(rotatedBuffer).metadata();
2238
2293
  meta[imageKey] = {
2239
2294
  o: { w: metadata.width || 0, h: metadata.height || 0 }
2240
2295
  };
@@ -2253,7 +2308,10 @@ async function handleUpload(request) {
2253
2308
  } catch (error) {
2254
2309
  console.error("Failed to upload:", error);
2255
2310
  const message = error instanceof Error ? error.message : "Unknown error";
2256
- return jsonResponse({ error: `Failed to upload file: ${message}` }, { status: 500 });
2311
+ return jsonResponse(
2312
+ { error: `Failed to upload file: ${message}` },
2313
+ { status: 500 }
2314
+ );
2257
2315
  }
2258
2316
  }
2259
2317
  async function handleDelete(request) {
@@ -2383,9 +2441,11 @@ async function handleDeleteStream(request) {
2383
2441
  async start(controller) {
2384
2442
  const sendEvent = (data) => {
2385
2443
  try {
2386
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
2444
+ controller.enqueue(
2445
+ encoder.encode(`data: ${JSON.stringify(data)}
2387
2446
 
2388
- `));
2447
+ `)
2448
+ );
2389
2449
  } catch {
2390
2450
  }
2391
2451
  };
@@ -2564,7 +2624,7 @@ async function handleDeleteStream(request) {
2564
2624
  headers: {
2565
2625
  "Content-Type": "text/event-stream",
2566
2626
  "Cache-Control": "no-cache",
2567
- "Connection": "keep-alive"
2627
+ Connection: "keep-alive"
2568
2628
  }
2569
2629
  });
2570
2630
  }
@@ -2572,7 +2632,10 @@ async function handleCreateFolder(request) {
2572
2632
  try {
2573
2633
  const { parentPath, name } = await request.json();
2574
2634
  if (!name || typeof name !== "string") {
2575
- return jsonResponse({ error: "Folder name is required" }, { status: 400 });
2635
+ return jsonResponse(
2636
+ { error: "Folder name is required" },
2637
+ { status: 400 }
2638
+ );
2576
2639
  }
2577
2640
  const sanitizedName = slugifyFolderName(name);
2578
2641
  if (!sanitizedName) {
@@ -2585,11 +2648,17 @@ async function handleCreateFolder(request) {
2585
2648
  }
2586
2649
  try {
2587
2650
  await fs7.access(folderPath);
2588
- 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
+ );
2589
2655
  } catch {
2590
2656
  }
2591
2657
  await fs7.mkdir(folderPath, { recursive: true });
2592
- return jsonResponse({ success: true, path: path7.join(safePath, sanitizedName) });
2658
+ return jsonResponse({
2659
+ success: true,
2660
+ path: path7.join(safePath, sanitizedName)
2661
+ });
2593
2662
  } catch (error) {
2594
2663
  console.error("Failed to create folder:", error);
2595
2664
  return jsonResponse({ error: "Failed to create folder" }, { status: 500 });
@@ -2600,7 +2669,10 @@ async function handleRename(request) {
2600
2669
  try {
2601
2670
  const { oldPath, newName } = await request.json();
2602
2671
  if (!oldPath || !newName) {
2603
- 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
+ );
2604
2676
  }
2605
2677
  const safePath = oldPath.replace(/\.\./g, "");
2606
2678
  const absoluteOldPath = getWorkspacePath(safePath);
@@ -2625,7 +2697,10 @@ async function handleRename(request) {
2625
2697
  isFile = stats.isFile();
2626
2698
  } catch {
2627
2699
  if (!isInCloud) {
2628
- return jsonResponse({ error: "File or folder not found" }, { status: 404 });
2700
+ return jsonResponse(
2701
+ { error: "File or folder not found" },
2702
+ { status: 404 }
2703
+ );
2629
2704
  }
2630
2705
  }
2631
2706
  const sanitizedName = isFile ? slugifyFilename(newName) : slugifyFolderName(newName);
@@ -2634,14 +2709,23 @@ async function handleRename(request) {
2634
2709
  }
2635
2710
  const parentDir = path7.dirname(absoluteOldPath);
2636
2711
  const absoluteNewPath = path7.join(parentDir, sanitizedName);
2637
- const newRelativePath = path7.join(path7.dirname(oldRelativePath), sanitizedName);
2712
+ const newRelativePath = path7.join(
2713
+ path7.dirname(oldRelativePath),
2714
+ sanitizedName
2715
+ );
2638
2716
  const newKey = "/" + newRelativePath;
2639
2717
  if (meta[newKey]) {
2640
- 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
+ );
2641
2722
  }
2642
2723
  try {
2643
2724
  await fs7.access(absoluteNewPath);
2644
- 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
+ );
2645
2729
  } catch {
2646
2730
  }
2647
2731
  if (isInOurR2 && !hasLocalFile) {
@@ -2699,7 +2783,10 @@ async function handleRenameStream(request) {
2699
2783
  try {
2700
2784
  const { oldPath, newName, operationId } = await request.json();
2701
2785
  if (!oldPath || !newName) {
2702
- sendEvent({ type: "error", message: "Path and new name are required" });
2786
+ sendEvent({
2787
+ type: "error",
2788
+ message: "Path and new name are required"
2789
+ });
2703
2790
  controller.close();
2704
2791
  return;
2705
2792
  }
@@ -2728,7 +2815,9 @@ async function handleRenameStream(request) {
2728
2815
  isFile = true;
2729
2816
  } else {
2730
2817
  const folderPrefix = oldKey2 + "/";
2731
- const hasChildrenInMeta = Object.keys(meta2).some((key) => key.startsWith(folderPrefix));
2818
+ const hasChildrenInMeta = Object.keys(meta2).some(
2819
+ (key) => key.startsWith(folderPrefix)
2820
+ );
2732
2821
  if (hasChildrenInMeta) {
2733
2822
  isFile = false;
2734
2823
  isVirtualFolder = true;
@@ -2747,14 +2836,20 @@ async function handleRenameStream(request) {
2747
2836
  }
2748
2837
  const parentDir = path7.dirname(absoluteOldPath);
2749
2838
  const absoluteNewPath = path7.join(parentDir, sanitizedName);
2750
- const newRelativePath = path7.join(path7.dirname(oldRelativePath), sanitizedName);
2839
+ const newRelativePath = path7.join(
2840
+ path7.dirname(oldRelativePath),
2841
+ sanitizedName
2842
+ );
2751
2843
  const newPath = path7.join(path7.dirname(safePath), sanitizedName);
2752
2844
  const meta = await loadMeta();
2753
2845
  const cdnUrls = getCdnUrls(meta);
2754
2846
  if (isFile) {
2755
2847
  const newKey2 = "/" + newRelativePath;
2756
2848
  if (meta[newKey2]) {
2757
- 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
+ });
2758
2853
  controller.close();
2759
2854
  return;
2760
2855
  }
@@ -2762,7 +2857,10 @@ async function handleRenameStream(request) {
2762
2857
  if (!isVirtualFolder) {
2763
2858
  try {
2764
2859
  await fs7.access(absoluteNewPath);
2765
- 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
+ });
2766
2864
  controller.close();
2767
2865
  return;
2768
2866
  } catch {
@@ -2770,9 +2868,14 @@ async function handleRenameStream(request) {
2770
2868
  }
2771
2869
  if (isVirtualFolder) {
2772
2870
  const newPrefix = "/" + newRelativePath + "/";
2773
- const hasConflict = Object.keys(meta).some((key) => key.startsWith(newPrefix));
2871
+ const hasConflict = Object.keys(meta).some(
2872
+ (key) => key.startsWith(newPrefix)
2873
+ );
2774
2874
  if (hasConflict) {
2775
- 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
+ });
2776
2879
  controller.close();
2777
2880
  return;
2778
2881
  }
@@ -2784,11 +2887,19 @@ async function handleRenameStream(request) {
2784
2887
  for (const [key, entry2] of Object.entries(meta)) {
2785
2888
  if (key.startsWith(oldPrefix) && entry2 && typeof entry2 === "object") {
2786
2889
  const newKey2 = key.replace(oldPrefix, newPrefix);
2787
- itemsToUpdate.push({ oldKey: key, newKey: newKey2, entry: entry2 });
2890
+ itemsToUpdate.push({
2891
+ oldKey: key,
2892
+ newKey: newKey2,
2893
+ entry: entry2
2894
+ });
2788
2895
  }
2789
2896
  }
2790
2897
  const total = itemsToUpdate.length + 1;
2791
- 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
+ });
2792
2903
  if (hasLocalItem) {
2793
2904
  await fs7.rename(absoluteOldPath, absoluteNewPath);
2794
2905
  const imagesDir = getPublicPath("/images");
@@ -2801,12 +2912,21 @@ async function handleRenameStream(request) {
2801
2912
  } catch {
2802
2913
  }
2803
2914
  }
2804
- 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
+ });
2805
2922
  let renamed = 1;
2806
2923
  const handleRenameCancel = async () => {
2807
2924
  await saveMeta(meta);
2808
2925
  await deleteEmptyFolders(absoluteOldPath);
2809
- const oldThumbFolder2 = path7.join(getPublicPath("/images"), oldRelativePath);
2926
+ const oldThumbFolder2 = path7.join(
2927
+ getPublicPath("/images"),
2928
+ oldRelativePath
2929
+ );
2810
2930
  await deleteEmptyFolders(oldThumbFolder2);
2811
2931
  sendEvent({ type: "complete", renamed, newPath, cancelled: true });
2812
2932
  controller.close();
@@ -2849,7 +2969,10 @@ async function handleRenameStream(request) {
2849
2969
  });
2850
2970
  }
2851
2971
  await deleteEmptyFolders(absoluteOldPath);
2852
- const oldThumbFolder = path7.join(getPublicPath("/images"), oldRelativePath);
2972
+ const oldThumbFolder = path7.join(
2973
+ getPublicPath("/images"),
2974
+ oldRelativePath
2975
+ );
2853
2976
  await deleteEmptyFolders(oldThumbFolder);
2854
2977
  sendEvent({ type: "complete", renamed, newPath });
2855
2978
  controller.close();
@@ -2972,7 +3095,9 @@ async function handleMoveStream(request) {
2972
3095
  const entries = await fs7.readdir(dir, { withFileTypes: true });
2973
3096
  for (const entry of entries) {
2974
3097
  if (entry.isDirectory()) {
2975
- count += await countFilesRecursive(path7.join(dir, entry.name));
3098
+ count += await countFilesRecursive(
3099
+ path7.join(dir, entry.name)
3100
+ );
2976
3101
  } else {
2977
3102
  count++;
2978
3103
  }
@@ -2994,7 +3119,15 @@ async function handleMoveStream(request) {
2994
3119
  }
2995
3120
  }
2996
3121
  totalFiles += localFileCount + cloudOnlyCount;
2997
- 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
+ });
2998
3131
  } else if (!hasLocalItem) {
2999
3132
  const folderPrefix = oldKey + "/";
3000
3133
  const virtualItems = [];
@@ -3002,20 +3135,49 @@ async function handleMoveStream(request) {
3002
3135
  if (key.startsWith(folderPrefix) && metaEntry && typeof metaEntry === "object") {
3003
3136
  const relativePath = key.slice(folderPrefix.length);
3004
3137
  const destNewKey = newKey + "/" + relativePath;
3005
- virtualItems.push({ oldKey: key, newKey: destNewKey, entry: metaEntry });
3138
+ virtualItems.push({
3139
+ oldKey: key,
3140
+ newKey: destNewKey,
3141
+ entry: metaEntry
3142
+ });
3006
3143
  }
3007
3144
  }
3008
3145
  if (virtualItems.length > 0) {
3009
3146
  totalFiles += virtualItems.length;
3010
- 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
+ });
3011
3157
  sourceFolders.add(absolutePath);
3012
3158
  } else {
3013
3159
  totalFiles++;
3014
- 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
+ });
3015
3169
  }
3016
3170
  } else {
3017
3171
  totalFiles++;
3018
- 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
+ });
3019
3181
  }
3020
3182
  }
3021
3183
  sendEvent({ type: "start", total: totalFiles });
@@ -3027,7 +3189,13 @@ async function handleMoveStream(request) {
3027
3189
  await deleteEmptyFolders(folder);
3028
3190
  }
3029
3191
  await deleteEmptyFolders(absoluteDestination);
3030
- 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
+ });
3031
3199
  controller.close();
3032
3200
  };
3033
3201
  for (const expandedItem of expandedItems) {
@@ -3035,7 +3203,16 @@ async function handleMoveStream(request) {
3035
3203
  await handleCancel();
3036
3204
  return;
3037
3205
  }
3038
- 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;
3039
3216
  if (isVirtualFolder && virtualFolderItems) {
3040
3217
  for (const vItem of virtualFolderItems) {
3041
3218
  if (isCancelled()) {
@@ -3050,11 +3227,18 @@ async function handleMoveStream(request) {
3050
3227
  let vItemMoved = false;
3051
3228
  if (isItemInR2) {
3052
3229
  try {
3053
- await moveInCdn(vItem.oldKey, vItem.newKey, itemHasThumbnails);
3230
+ await moveInCdn(
3231
+ vItem.oldKey,
3232
+ vItem.newKey,
3233
+ itemHasThumbnails
3234
+ );
3054
3235
  vItemMoved = true;
3055
3236
  filesMoved++;
3056
3237
  } catch (err) {
3057
- console.error(`Failed to move cloud item ${vItem.oldKey}:`, err);
3238
+ console.error(
3239
+ `Failed to move cloud item ${vItem.oldKey}:`,
3240
+ err
3241
+ );
3058
3242
  delete meta[vItem.oldKey];
3059
3243
  await saveMeta(meta);
3060
3244
  }
@@ -3078,7 +3262,10 @@ async function handleMoveStream(request) {
3078
3262
  }
3079
3263
  const newFolderPath = getPublicPath(newKey);
3080
3264
  await deleteEmptyFolders(newFolderPath);
3081
- const newThumbFolder = path7.join(getPublicPath("images"), newKey.slice(1));
3265
+ const newThumbFolder = path7.join(
3266
+ getPublicPath("images"),
3267
+ newKey.slice(1)
3268
+ );
3082
3269
  await deleteEmptyFolders(newThumbFolder);
3083
3270
  const oldFolderPath = getPublicPath(oldKey);
3084
3271
  sourceFolders.add(oldFolderPath);
@@ -3111,7 +3298,9 @@ async function handleMoveStream(request) {
3111
3298
  if (isRemote) {
3112
3299
  const remoteUrl = `${fileCdnUrl}${oldKey}`;
3113
3300
  const buffer = await downloadFromRemoteUrl(remoteUrl);
3114
- await fs7.mkdir(path7.dirname(newAbsolutePath), { recursive: true });
3301
+ await fs7.mkdir(path7.dirname(newAbsolutePath), {
3302
+ recursive: true
3303
+ });
3115
3304
  await fs7.writeFile(newAbsolutePath, buffer);
3116
3305
  const newEntry = {
3117
3306
  o: entry?.o,
@@ -3196,7 +3385,9 @@ async function handleMoveStream(request) {
3196
3385
  }
3197
3386
  const stats = await fs7.stat(absolutePath);
3198
3387
  if (stats.isFile()) {
3199
- await fs7.mkdir(path7.dirname(newAbsolutePath), { recursive: true });
3388
+ await fs7.mkdir(path7.dirname(newAbsolutePath), {
3389
+ recursive: true
3390
+ });
3200
3391
  await fs7.rename(absolutePath, newAbsolutePath);
3201
3392
  if (isImage && entry) {
3202
3393
  const oldThumbPaths = getAllThumbnailPaths(oldKey);
@@ -3207,7 +3398,9 @@ async function handleMoveStream(request) {
3207
3398
  try {
3208
3399
  await fs7.access(oldThumbPath);
3209
3400
  sourceFolders.add(path7.dirname(oldThumbPath));
3210
- await fs7.mkdir(path7.dirname(newThumbPath), { recursive: true });
3401
+ await fs7.mkdir(path7.dirname(newThumbPath), {
3402
+ recursive: true
3403
+ });
3211
3404
  await fs7.rename(oldThumbPath, newThumbPath);
3212
3405
  } catch {
3213
3406
  }
@@ -3243,13 +3436,21 @@ async function handleMoveStream(request) {
3243
3436
  const newPrefix = newKey + "/";
3244
3437
  const localFiles = [];
3245
3438
  const collectLocalFiles = async (dir, relativeDir) => {
3246
- const entries = await fs7.readdir(dir, { withFileTypes: true });
3439
+ const entries = await fs7.readdir(dir, {
3440
+ withFileTypes: true
3441
+ });
3247
3442
  for (const dirEntry of entries) {
3248
3443
  const entryRelPath = relativeDir ? `${relativeDir}/${dirEntry.name}` : dirEntry.name;
3249
3444
  if (dirEntry.isDirectory()) {
3250
- await collectLocalFiles(path7.join(dir, dirEntry.name), entryRelPath);
3445
+ await collectLocalFiles(
3446
+ path7.join(dir, dirEntry.name),
3447
+ entryRelPath
3448
+ );
3251
3449
  } else {
3252
- localFiles.push({ relativePath: entryRelPath, isImage: isImageFile(dirEntry.name) });
3450
+ localFiles.push({
3451
+ relativePath: entryRelPath,
3452
+ isImage: isImageFile(dirEntry.name)
3453
+ });
3253
3454
  }
3254
3455
  }
3255
3456
  };
@@ -3275,13 +3476,21 @@ async function handleMoveStream(request) {
3275
3476
  await handleCancel();
3276
3477
  return;
3277
3478
  }
3278
- const fileOldPath = path7.join(absolutePath, localFile.relativePath);
3279
- 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
+ );
3280
3487
  const fileOldKey = oldPrefix + localFile.relativePath;
3281
3488
  const fileNewKey = newPrefix + localFile.relativePath;
3282
3489
  const fileEntry = meta[fileOldKey];
3283
3490
  sourceFolders.add(path7.dirname(fileOldPath));
3284
- await fs7.mkdir(path7.dirname(fileNewPath), { recursive: true });
3491
+ await fs7.mkdir(path7.dirname(fileNewPath), {
3492
+ recursive: true
3493
+ });
3285
3494
  await fs7.rename(fileOldPath, fileNewPath);
3286
3495
  filesMoved++;
3287
3496
  if (localFile.isImage && fileEntry) {
@@ -3293,7 +3502,9 @@ async function handleMoveStream(request) {
3293
3502
  try {
3294
3503
  await fs7.access(oldThumbPath);
3295
3504
  sourceFolders.add(path7.dirname(oldThumbPath));
3296
- await fs7.mkdir(path7.dirname(newThumbPath), { recursive: true });
3505
+ await fs7.mkdir(path7.dirname(newThumbPath), {
3506
+ recursive: true
3507
+ });
3297
3508
  await fs7.rename(oldThumbPath, newThumbPath);
3298
3509
  } catch {
3299
3510
  }
@@ -3332,11 +3543,18 @@ async function handleMoveStream(request) {
3332
3543
  let cloudFileMoved = false;
3333
3544
  if (cloudIsInR2) {
3334
3545
  try {
3335
- await moveInCdn(cloudFile.oldKey, cloudFile.newKey, cloudHasThumbs);
3546
+ await moveInCdn(
3547
+ cloudFile.oldKey,
3548
+ cloudFile.newKey,
3549
+ cloudHasThumbs
3550
+ );
3336
3551
  cloudFileMoved = true;
3337
3552
  filesMoved++;
3338
3553
  } catch (err) {
3339
- console.error(`Failed to move cloud file ${cloudFile.oldKey}:`, err);
3554
+ console.error(
3555
+ `Failed to move cloud file ${cloudFile.oldKey}:`,
3556
+ err
3557
+ );
3340
3558
  delete meta[cloudFile.oldKey];
3341
3559
  await saveMeta(meta);
3342
3560
  }
@@ -3358,7 +3576,10 @@ async function handleMoveStream(request) {
3358
3576
  }
3359
3577
  sourceFolders.add(absolutePath);
3360
3578
  const oldThumbRelPath = oldKey.slice(1);
3361
- const oldThumbFolder = path7.join(getPublicPath("images"), oldThumbRelPath);
3579
+ const oldThumbFolder = path7.join(
3580
+ getPublicPath("images"),
3581
+ oldThumbRelPath
3582
+ );
3362
3583
  sourceFolders.add(oldThumbFolder);
3363
3584
  moved.push(itemPath);
3364
3585
  }
@@ -3400,7 +3621,7 @@ async function handleMoveStream(request) {
3400
3621
  headers: {
3401
3622
  "Content-Type": "text/event-stream",
3402
3623
  "Cache-Control": "no-cache",
3403
- "Connection": "keep-alive"
3624
+ Connection: "keep-alive"
3404
3625
  }
3405
3626
  });
3406
3627
  }
@@ -3420,7 +3641,9 @@ async function handleScanStream() {
3420
3641
  };
3421
3642
  try {
3422
3643
  const meta = await loadMeta();
3423
- const existingCount = Object.keys(meta).filter((k) => !k.startsWith("_")).length;
3644
+ const existingCount = Object.keys(meta).filter(
3645
+ (k) => !k.startsWith("_")
3646
+ ).length;
3424
3647
  const existingKeys = new Set(Object.keys(meta));
3425
3648
  const added = [];
3426
3649
  const renamed = [];
@@ -3435,7 +3658,8 @@ async function handleScanStream() {
3435
3658
  if (entry.name.startsWith(".")) continue;
3436
3659
  const fullPath = path8.join(dir, entry.name);
3437
3660
  const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
3438
- if (relPath === "images" || relPath.startsWith("images/")) continue;
3661
+ if (relPath === "images" || relPath.startsWith("images/"))
3662
+ continue;
3439
3663
  if (entry.isDirectory()) {
3440
3664
  await scanDir(fullPath, relPath);
3441
3665
  } else if (isMediaFile(entry.name)) {
@@ -3519,7 +3743,8 @@ async function handleScanStream() {
3519
3743
  } else {
3520
3744
  try {
3521
3745
  const buffer = await fs8.readFile(fullPath);
3522
- const metadata = await sharp3(buffer).metadata();
3746
+ const rotatedBuffer = await sharp3(buffer).rotate().toBuffer();
3747
+ const metadata = await sharp3(rotatedBuffer).metadata();
3523
3748
  meta[imageKey] = {
3524
3749
  o: { w: metadata.width || 0, h: metadata.height || 0 }
3525
3750
  };
@@ -3540,7 +3765,10 @@ async function handleScanStream() {
3540
3765
  errors.push(relativePath);
3541
3766
  }
3542
3767
  }
3543
- sendEvent({ type: "cleanup", message: "Checking for orphaned thumbnails..." });
3768
+ sendEvent({
3769
+ type: "cleanup",
3770
+ message: "Checking for orphaned thumbnails..."
3771
+ });
3544
3772
  const expectedThumbnails = /* @__PURE__ */ new Set();
3545
3773
  const fileEntries = getFileEntries(meta);
3546
3774
  for (const [imageKey, entry] of fileEntries) {
@@ -3587,7 +3815,9 @@ async function handleScanStream() {
3587
3815
  await cleanEmptyFolders(fullPath);
3588
3816
  try {
3589
3817
  const subEntries = await fs8.readdir(fullPath);
3590
- const meaningfulEntries = subEntries.filter((e) => !e.startsWith("."));
3818
+ const meaningfulEntries = subEntries.filter(
3819
+ (e) => !e.startsWith(".")
3820
+ );
3591
3821
  if (meaningfulEntries.length === 0) {
3592
3822
  await fs8.rm(fullPath, { recursive: true });
3593
3823
  emptyFoldersDeleted++;
@@ -3605,7 +3835,9 @@ async function handleScanStream() {
3605
3835
  let isEmpty = true;
3606
3836
  for (const entry of entries) {
3607
3837
  if (entry.isDirectory()) {
3608
- const subDirEmpty = await cleanImagesEmptyFolders(path8.join(dir, entry.name));
3838
+ const subDirEmpty = await cleanImagesEmptyFolders(
3839
+ path8.join(dir, entry.name)
3840
+ );
3609
3841
  if (!subDirEmpty) isEmpty = false;
3610
3842
  } else if (!entry.name.startsWith(".")) {
3611
3843
  isEmpty = false;
@@ -3624,7 +3856,10 @@ async function handleScanStream() {
3624
3856
  await cleanImagesEmptyFolders(imagesDir);
3625
3857
  } catch {
3626
3858
  }
3627
- sendEvent({ type: "cleanup", message: "Checking for orphaned entries..." });
3859
+ sendEvent({
3860
+ type: "cleanup",
3861
+ message: "Checking for orphaned entries..."
3862
+ });
3628
3863
  const orphanedEntries = [];
3629
3864
  const cdnUrls = meta._cdns || [];
3630
3865
  const r2PublicUrl = (process.env.CLOUDFLARE_R2_PUBLIC_URL || "").replace(/\/$/, "");
@@ -3652,7 +3887,10 @@ async function handleScanStream() {
3652
3887
  }
3653
3888
  }
3654
3889
  if (orphanedEntries.length > 0) {
3655
- sendEvent({ type: "cleanup", message: `Removed ${orphanedEntries.length} orphaned entries...` });
3890
+ sendEvent({
3891
+ type: "cleanup",
3892
+ message: `Removed ${orphanedEntries.length} orphaned entries...`
3893
+ });
3656
3894
  }
3657
3895
  await saveMeta(meta);
3658
3896
  sendEvent({
@@ -3679,7 +3917,7 @@ async function handleScanStream() {
3679
3917
  headers: {
3680
3918
  "Content-Type": "text/event-stream",
3681
3919
  "Cache-Control": "no-cache",
3682
- "Connection": "keep-alive"
3920
+ Connection: "keep-alive"
3683
3921
  }
3684
3922
  });
3685
3923
  }
@@ -3717,7 +3955,10 @@ async function handleDeleteOrphans(request) {
3717
3955
  });
3718
3956
  } catch (error) {
3719
3957
  console.error("Failed to delete orphans:", error);
3720
- return jsonResponse({ error: "Failed to delete orphaned files" }, { status: 500 });
3958
+ return jsonResponse(
3959
+ { error: "Failed to delete orphaned files" },
3960
+ { status: 500 }
3961
+ );
3721
3962
  }
3722
3963
  }
3723
3964
 
@@ -3735,9 +3976,11 @@ async function processRemoteImage(url) {
3735
3976
  throw new Error(`Failed to fetch: ${response.status}`);
3736
3977
  }
3737
3978
  const buffer = Buffer.from(await response.arrayBuffer());
3738
- const metadata = await sharp4(buffer).metadata();
3979
+ const rotatedBuffer = await sharp4(buffer).rotate().toBuffer();
3980
+ const metadata = await sharp4(rotatedBuffer).metadata();
3739
3981
  return {
3740
3982
  o: { w: metadata.width || 0, h: metadata.height || 0 }
3983
+ // b: blur hash would be generated here if needed
3741
3984
  };
3742
3985
  }
3743
3986
  async function handleImportUrls(request) {
@@ -3746,9 +3989,11 @@ async function handleImportUrls(request) {
3746
3989
  async start(controller) {
3747
3990
  const sendEvent = (data) => {
3748
3991
  try {
3749
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
3992
+ controller.enqueue(
3993
+ encoder.encode(`data: ${JSON.stringify(data)}
3750
3994
 
3751
- `));
3995
+ `)
3996
+ );
3752
3997
  } catch {
3753
3998
  }
3754
3999
  };
@@ -3848,7 +4093,7 @@ async function handleImportUrls(request) {
3848
4093
  headers: {
3849
4094
  "Content-Type": "text/event-stream",
3850
4095
  "Cache-Control": "no-cache",
3851
- "Connection": "keep-alive"
4096
+ Connection: "keep-alive"
3852
4097
  }
3853
4098
  });
3854
4099
  }
@@ -3901,9 +4146,12 @@ async function handleGenerateFavicon(request) {
3901
4146
  }
3902
4147
  const fileName = path9.basename(imagePath).toLowerCase();
3903
4148
  if (fileName !== "favicon.png" && fileName !== "favicon.jpg") {
3904
- return jsonResponse({
3905
- error: "Source file must be named favicon.png or favicon.jpg"
3906
- }, { status: 400 });
4149
+ return jsonResponse(
4150
+ {
4151
+ error: "Source file must be named favicon.png or favicon.jpg"
4152
+ },
4153
+ { status: 400 }
4154
+ );
3907
4155
  }
3908
4156
  const sourcePath = getPublicPath(imagePath.replace(/^\//, ""));
3909
4157
  try {
@@ -3913,17 +4161,24 @@ async function handleGenerateFavicon(request) {
3913
4161
  }
3914
4162
  let metadata;
3915
4163
  try {
3916
- metadata = await sharp5(sourcePath).metadata();
4164
+ const rotatedBuffer = await sharp5(sourcePath).rotate().toBuffer();
4165
+ metadata = await sharp5(rotatedBuffer).metadata();
3917
4166
  } catch {
3918
- 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
+ );
3919
4171
  }
3920
4172
  const outputDir = getSrcAppPath();
3921
4173
  try {
3922
4174
  await fs9.access(outputDir);
3923
4175
  } catch {
3924
- return jsonResponse({
3925
- error: "Output directory src/app/ not found"
3926
- }, { status: 500 });
4176
+ return jsonResponse(
4177
+ {
4178
+ error: "Output directory src/app/ not found"
4179
+ },
4180
+ { status: 500 }
4181
+ );
3927
4182
  }
3928
4183
  const stream = new ReadableStream({
3929
4184
  async start(controller) {
@@ -3945,7 +4200,7 @@ async function handleGenerateFavicon(request) {
3945
4200
  const config = FAVICON_CONFIGS[i];
3946
4201
  try {
3947
4202
  const outputPath = path9.join(outputDir, config.name);
3948
- await sharp5(sourcePath).resize(config.size, config.size, {
4203
+ await sharp5(sourcePath).rotate().resize(config.size, config.size, {
3949
4204
  fit: "cover",
3950
4205
  position: "center"
3951
4206
  }).png({ quality: 100 }).toFile(outputPath);
@@ -4036,23 +4291,20 @@ async function handleGenerateFeaturedImage(request) {
4036
4291
  };
4037
4292
  try {
4038
4293
  const packageJsonPath2 = getWorkspacePath("package.json");
4039
- let projectName = "featured-image";
4040
4294
  let homepageUrl = customUrl || process.env.STUDIO_DEV_SITE_URL || "http://localhost:3000";
4041
4295
  try {
4042
4296
  const packageJsonContent = await fs10.readFile(packageJsonPath2, "utf8");
4043
4297
  const packageJson2 = JSON.parse(packageJsonContent);
4044
- projectName = packageJson2.name || "featured-image";
4045
4298
  if (!customUrl && packageJson2.homepage) {
4046
4299
  homepageUrl = packageJson2.homepage;
4047
4300
  }
4048
4301
  } catch {
4049
4302
  }
4050
- const outputPath = getPublicPath(`${projectName}.jpg`);
4051
- const relativePath = `public/${projectName}.jpg`;
4303
+ const outputPath = getPublicPath(`featured.jpg`);
4304
+ const relativePath = `public/featured.jpg`;
4052
4305
  sendEvent({
4053
4306
  type: "start",
4054
4307
  total: 4,
4055
- projectName,
4056
4308
  url: homepageUrl,
4057
4309
  output: relativePath
4058
4310
  });
@@ -4111,7 +4363,7 @@ async function handleGenerateFeaturedImage(request) {
4111
4363
  const width = metadata.width || 0;
4112
4364
  const height = metadata.height || 0;
4113
4365
  const meta = await loadMeta();
4114
- const metaKey = `/${projectName}.jpg`;
4366
+ const metaKey = `/featured.jpg`;
4115
4367
  setMetaEntry(meta, metaKey, {
4116
4368
  o: { w: width, h: height }
4117
4369
  });
@@ -4187,22 +4439,13 @@ async function handleGetFeaturedImageOptions() {
4187
4439
  }
4188
4440
  async function handleCheckFeaturedImage() {
4189
4441
  try {
4190
- const packageJsonPath2 = getWorkspacePath("package.json");
4191
- let projectName = "featured-image";
4192
- try {
4193
- const packageJsonContent = await fs10.readFile(packageJsonPath2, "utf8");
4194
- const packageJson2 = JSON.parse(packageJsonContent);
4195
- projectName = packageJson2.name || "featured-image";
4196
- } catch {
4197
- }
4198
- const expectedFilename = `${projectName}.jpg`;
4199
- const metaKey = `/${projectName}.jpg`;
4442
+ const expectedFilename = `featured.jpg`;
4443
+ const metaKey = `/featured.jpg`;
4200
4444
  const meta = await loadMeta();
4201
4445
  const exists = metaKey in meta && !Array.isArray(meta[metaKey]);
4202
4446
  return jsonResponse({
4203
4447
  filename: expectedFilename,
4204
- exists,
4205
- projectName
4448
+ exists
4206
4449
  });
4207
4450
  } catch (error) {
4208
4451
  console.error("Check featured image error:", error);