@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.
- package/README.md +0 -15
- package/dist/client/assets/index-CidaYZsl.js +79 -0
- package/dist/client/index.html +1 -1
- package/dist/server/index.js +11 -138
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/client/index.html
CHANGED
|
@@ -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-
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-CidaYZsl.js"></script>
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
package/dist/server/index.js
CHANGED
|
@@ -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));
|