@gallop.software/studio 2.2.15 → 2.3.1

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.
@@ -11,7 +11,7 @@
11
11
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
12
12
  }
13
13
  </style>
14
- <script type="module" crossorigin src="/assets/index-DSF2nGpu.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-CidaYZsl.js"></script>
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -226,47 +226,6 @@ function isProcessed(entry) {
226
226
  }
227
227
 
228
228
  // src/handlers/utils/cdn.ts
229
- async function purgeCloudflareCache(urls) {
230
- const zoneId = process.env.CLOUDFLARE_ZONE_ID;
231
- const apiToken = process.env.CLOUDFLARE_API_TOKEN;
232
- if (urls.length === 0) {
233
- return { status: "success" };
234
- }
235
- if (!zoneId || !apiToken) {
236
- return {
237
- status: "not_configured",
238
- message: "Cache purge skipped. To enable, add CLOUDFLARE_ZONE_ID and CLOUDFLARE_API_TOKEN to .env.studio"
239
- };
240
- }
241
- try {
242
- const response = await fetch(
243
- `https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`,
244
- {
245
- method: "POST",
246
- headers: {
247
- "Authorization": `Bearer ${apiToken}`,
248
- "Content-Type": "application/json"
249
- },
250
- body: JSON.stringify({ files: urls })
251
- }
252
- );
253
- if (!response.ok) {
254
- const text = await response.text();
255
- console.error("Cache purge failed:", text);
256
- return {
257
- status: "failed",
258
- message: "Cache purge failed. Check CLOUDFLARE_ZONE_ID and CLOUDFLARE_API_TOKEN in .env.studio"
259
- };
260
- }
261
- return { status: "success", message: "Cache cleared successfully." };
262
- } catch (error) {
263
- console.error("Cache purge error:", error);
264
- return {
265
- status: "failed",
266
- message: "Cache purge failed. Check your network connection."
267
- };
268
- }
269
- }
270
229
  function getR2Client() {
271
230
  const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;
272
231
  const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
@@ -1507,7 +1466,7 @@ async function handleMoveStream(request) {
1507
1466
  // src/handlers/images.ts
1508
1467
  import { promises as fs7 } from "fs";
1509
1468
  import path7 from "path";
1510
- import { S3Client as S3Client2, PutObjectCommand as PutObjectCommand2 } from "@aws-sdk/client-s3";
1469
+ import { S3Client as S3Client2, PutObjectCommand as PutObjectCommand2, DeleteObjectCommand as DeleteObjectCommand2 } from "@aws-sdk/client-s3";
1511
1470
  async function handleSync(request) {
1512
1471
  const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;
1513
1472
  const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
@@ -1535,7 +1494,6 @@ async function handleSync(request) {
1535
1494
  });
1536
1495
  const pushed = [];
1537
1496
  const errors = [];
1538
- const urlsToPurge = [];
1539
1497
  const sourceFolders = /* @__PURE__ */ new Set();
1540
1498
  for (let imageKey of imageKeys) {
1541
1499
  if (!imageKey.startsWith("/")) {
@@ -1575,7 +1533,6 @@ async function handleSync(request) {
1575
1533
  ContentType: getContentType(imageKey)
1576
1534
  })
1577
1535
  );
1578
- urlsToPurge.push(`${publicUrl}${imageKey}`);
1579
1536
  if (!isRemote && isProcessed(entry)) {
1580
1537
  for (const thumbPath of getAllThumbnailPaths(imageKey)) {
1581
1538
  const localPath = getPublicPath(thumbPath);
@@ -1589,7 +1546,6 @@ async function handleSync(request) {
1589
1546
  ContentType: getContentType(thumbPath)
1590
1547
  })
1591
1548
  );
1592
- urlsToPurge.push(`${publicUrl}${thumbPath}`);
1593
1549
  } catch {
1594
1550
  }
1595
1551
  }
@@ -1621,18 +1577,10 @@ async function handleSync(request) {
1621
1577
  for (const folder of sourceFolders) {
1622
1578
  await deleteEmptyFolders(folder);
1623
1579
  }
1624
- let cacheMessage;
1625
- if (urlsToPurge.length > 0) {
1626
- const cacheResult = await purgeCloudflareCache(urlsToPurge);
1627
- if (cacheResult.message) {
1628
- cacheMessage = cacheResult.message;
1629
- }
1630
- }
1631
1580
  return jsonResponse({
1632
1581
  success: true,
1633
1582
  pushed,
1634
- errors: errors.length > 0 ? errors : void 0,
1635
- cacheMessage
1583
+ errors: errors.length > 0 ? errors : void 0
1636
1584
  });
1637
1585
  } catch (error) {
1638
1586
  console.error("Failed to push:", error);
@@ -1665,7 +1613,6 @@ async function handleUnprocessStream(request) {
1665
1613
  const removed = [];
1666
1614
  const skipped = [];
1667
1615
  const errors = [];
1668
- const urlsToPurge = [];
1669
1616
  const total = imageKeys.length;
1670
1617
  sendEvent({ type: "start", total });
1671
1618
  for (let i = 0; i < imageKeys.length; i++) {
@@ -1697,9 +1644,6 @@ async function handleUnprocessStream(request) {
1697
1644
  await deleteLocalThumbnails(imageKey);
1698
1645
  if (isInOurR2) {
1699
1646
  await deleteThumbnailsFromCdn(imageKey);
1700
- for (const thumbPath of getAllThumbnailPaths(imageKey)) {
1701
- urlsToPurge.push(`${publicUrl}${thumbPath}`);
1702
- }
1703
1647
  }
1704
1648
  meta[imageKey] = {
1705
1649
  o: entry.o,
@@ -1714,14 +1658,6 @@ async function handleUnprocessStream(request) {
1714
1658
  }
1715
1659
  sendEvent({ type: "cleanup", message: "Saving metadata..." });
1716
1660
  await saveMeta(meta);
1717
- let cacheMessage = "";
1718
- if (urlsToPurge.length > 0) {
1719
- sendEvent({ type: "cleanup", message: "Purging CDN cache..." });
1720
- const cacheResult = await purgeCloudflareCache(urlsToPurge);
1721
- if (cacheResult.message) {
1722
- cacheMessage = ` ${cacheResult.message}`;
1723
- }
1724
- }
1725
1661
  sendEvent({ type: "cleanup", message: "Cleaning up empty folders..." });
1726
1662
  const imagesDir = getPublicPath("images");
1727
1663
  try {
@@ -1735,7 +1671,6 @@ async function handleUnprocessStream(request) {
1735
1671
  if (errors.length > 0) {
1736
1672
  message += ` ${errors.length} image${errors.length !== 1 ? "s" : ""} failed.`;
1737
1673
  }
1738
- message += cacheMessage;
1739
1674
  sendEvent({
1740
1675
  type: "complete",
1741
1676
  processed: removed.length,
@@ -1784,7 +1719,6 @@ async function handleReprocessStream(request) {
1784
1719
  const cdnUrls = getCdnUrls(meta);
1785
1720
  const processed = [];
1786
1721
  const errors = [];
1787
- const urlsToPurge = [];
1788
1722
  const total = imageKeys.length;
1789
1723
  sendEvent({ type: "start", total });
1790
1724
  for (let i = 0; i < imageKeys.length; i++) {
@@ -1847,10 +1781,8 @@ async function handleReprocessStream(request) {
1847
1781
  const updatedEntry = await processImage(buffer, imageKey);
1848
1782
  if (isInOurR2) {
1849
1783
  updatedEntry.c = existingCdnIndex;
1784
+ await deleteThumbnailsFromCdn(imageKey);
1850
1785
  await uploadToCdn(imageKey);
1851
- for (const thumbPath of getAllThumbnailPaths(imageKey)) {
1852
- urlsToPurge.push(`${publicUrl}${thumbPath}`);
1853
- }
1854
1786
  await deleteLocalThumbnails(imageKey);
1855
1787
  try {
1856
1788
  await fs7.unlink(originalPath);
@@ -1867,19 +1799,10 @@ async function handleReprocessStream(request) {
1867
1799
  }
1868
1800
  sendEvent({ type: "cleanup", message: "Saving metadata..." });
1869
1801
  await saveMeta(meta);
1870
- let cacheMessage = "";
1871
- if (urlsToPurge.length > 0) {
1872
- sendEvent({ type: "cleanup", message: "Purging CDN cache..." });
1873
- const cacheResult = await purgeCloudflareCache(urlsToPurge);
1874
- if (cacheResult.message) {
1875
- cacheMessage = ` ${cacheResult.message}`;
1876
- }
1877
- }
1878
1802
  let message = `Generated thumbnails for ${processed.length} image${processed.length !== 1 ? "s" : ""}.`;
1879
1803
  if (errors.length > 0) {
1880
1804
  message += ` ${errors.length} image${errors.length !== 1 ? "s" : ""} failed.`;
1881
1805
  }
1882
- message += cacheMessage;
1883
1806
  sendEvent({
1884
1807
  type: "complete",
1885
1808
  processed: processed.length,
@@ -2035,7 +1958,6 @@ async function handlePushUpdatesStream(request) {
2035
1958
  const pushed = [];
2036
1959
  const skipped = [];
2037
1960
  const errors = [];
2038
- const urlsToPurge = [];
2039
1961
  const total = paths.length;
2040
1962
  sendEvent({ type: "start", total });
2041
1963
  for (let i = 0; i < paths.length; i++) {
@@ -2063,6 +1985,13 @@ async function handlePushUpdatesStream(request) {
2063
1985
  const buffer = await fs7.readFile(localPath);
2064
1986
  const contentType = getContentType(path7.basename(key));
2065
1987
  const uploadKey = key.startsWith("/") ? key.slice(1) : key;
1988
+ try {
1989
+ await s3.send(new DeleteObjectCommand2({
1990
+ Bucket: bucketName,
1991
+ Key: uploadKey
1992
+ }));
1993
+ } catch {
1994
+ }
2066
1995
  await s3.send(new PutObjectCommand2({
2067
1996
  Bucket: bucketName,
2068
1997
  Key: uploadKey,
@@ -2070,17 +1999,14 @@ async function handlePushUpdatesStream(request) {
2070
1999
  ContentType: contentType
2071
2000
  }));
2072
2001
  if (isProcessed(entry)) {
2002
+ await deleteThumbnailsFromCdn(key);
2073
2003
  const processedEntry = await processImage(buffer, key);
2074
2004
  Object.assign(entry, processedEntry);
2075
2005
  await uploadToCdn(key);
2076
2006
  await deleteLocalThumbnails(key);
2077
- for (const thumbPath of getAllThumbnailPaths(key)) {
2078
- urlsToPurge.push(`${publicUrl}${thumbPath}`);
2079
- }
2080
2007
  }
2081
2008
  await fs7.unlink(localPath);
2082
2009
  delete entry.u;
2083
- urlsToPurge.push(`${publicUrl}${key}`);
2084
2010
  pushed.push(key);
2085
2011
  } catch (error) {
2086
2012
  console.error(`Failed to push update for ${key}:`, error);
@@ -2093,18 +2019,6 @@ async function handlePushUpdatesStream(request) {
2093
2019
  await deleteEmptyFolders(path7.dirname(localPath));
2094
2020
  }
2095
2021
  await saveMeta(meta);
2096
- let cacheMessage = "";
2097
- if (urlsToPurge.length > 0) {
2098
- sendEvent({ type: "cleanup", message: "Purging CDN cache..." });
2099
- const cacheResult = await purgeCloudflareCache(urlsToPurge);
2100
- if (cacheResult.status === "not_configured") {
2101
- cacheMessage = ` ${cacheResult.message}`;
2102
- } else if (cacheResult.status === "failed") {
2103
- cacheMessage = ` ${cacheResult.message}`;
2104
- } else if (cacheResult.status === "success" && cacheResult.message) {
2105
- cacheMessage = ` ${cacheResult.message}`;
2106
- }
2107
- }
2108
2022
  let message = `Pushed ${pushed.length} update${pushed.length !== 1 ? "s" : ""} to cloud.`;
2109
2023
  if (skipped.length > 0) {
2110
2024
  message += ` ${skipped.length} file${skipped.length !== 1 ? "s" : ""} skipped.`;
@@ -2112,7 +2026,6 @@ async function handlePushUpdatesStream(request) {
2112
2026
  if (errors.length > 0) {
2113
2027
  message += ` ${errors.length} file${errors.length !== 1 ? "s" : ""} failed.`;
2114
2028
  }
2115
- message += cacheMessage;
2116
2029
  sendEvent({
2117
2030
  type: "complete",
2118
2031
  pushed: pushed.length,
@@ -2180,45 +2093,6 @@ async function handleCancelUpdates(request) {
2180
2093
  return jsonResponse({ error: "Failed to cancel updates" }, { status: 500 });
2181
2094
  }
2182
2095
  }
2183
- async function handleClearCache(request) {
2184
- const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\/$/, "");
2185
- try {
2186
- const { paths } = await request.json();
2187
- if (!paths || !Array.isArray(paths) || paths.length === 0) {
2188
- return jsonResponse({ error: "No paths provided" }, { status: 400 });
2189
- }
2190
- const meta = await loadMeta();
2191
- const cdnUrls = getCdnUrls(meta);
2192
- const urlsToPurge = [];
2193
- for (const itemPath of paths) {
2194
- const key = itemPath.startsWith("public/") ? "/" + itemPath.slice(7) : itemPath;
2195
- const entry = meta[key];
2196
- if (!entry || entry.c === void 0) continue;
2197
- const cdnUrl = cdnUrls[entry.c]?.replace(/\/$/, "");
2198
- if (!cdnUrl) continue;
2199
- urlsToPurge.push(`${cdnUrl}${key}`);
2200
- if (entry.sm || entry.md || entry.lg || entry.f) {
2201
- for (const thumbPath of getAllThumbnailPaths(key)) {
2202
- urlsToPurge.push(`${cdnUrl}${thumbPath}`);
2203
- }
2204
- }
2205
- }
2206
- if (urlsToPurge.length === 0) {
2207
- return jsonResponse({
2208
- success: true,
2209
- message: "No CDN files to clear cache for."
2210
- });
2211
- }
2212
- const cacheResult = await purgeCloudflareCache(urlsToPurge);
2213
- return jsonResponse({
2214
- success: cacheResult.status === "success",
2215
- message: cacheResult.message || `Cleared cache for ${urlsToPurge.length} URLs.`
2216
- });
2217
- } catch (error) {
2218
- console.error("Clear cache error:", error);
2219
- return jsonResponse({ error: "Failed to clear cache" }, { status: 500 });
2220
- }
2221
- }
2222
2096
 
2223
2097
  // src/handlers/scan.ts
2224
2098
  import { promises as fs8 } from "fs";
@@ -2788,7 +2662,6 @@ async function startServer(options) {
2788
2662
  app.post("/api/studio/download-stream", wrapHandler(handleDownloadStream, true));
2789
2663
  app.post("/api/studio/push-updates-stream", wrapHandler(handlePushUpdatesStream, true));
2790
2664
  app.post("/api/studio/cancel-updates", wrapHandler(handleCancelUpdates));
2791
- app.post("/api/studio/clear-cache", wrapHandler(handleClearCache));
2792
2665
  app.post("/api/studio/scan", wrapHandler(handleScanStream, true));
2793
2666
  app.post("/api/studio/delete-orphans", wrapHandler(handleDeleteOrphans));
2794
2667
  app.post("/api/studio/import", wrapHandler(handleImportUrls, true));