@gallop.software/studio 0.1.116 → 1.0.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/dist/{StudioUI-7LIOKKXE.js → StudioUI-GWMM47P7.js} +81 -15
- package/dist/StudioUI-GWMM47P7.js.map +1 -0
- package/dist/{StudioUI-CE7CEP63.mjs → StudioUI-KCUI5YUD.mjs} +81 -15
- package/dist/StudioUI-KCUI5YUD.mjs.map +1 -0
- package/dist/{chunk-RDNC5ABF.mjs → chunk-FDWPNRNZ.mjs} +1 -1
- package/dist/chunk-FDWPNRNZ.mjs.map +1 -0
- package/dist/{chunk-LEOQKJCL.js → chunk-WJJHVPLT.js} +1 -1
- package/dist/chunk-WJJHVPLT.js.map +1 -0
- package/dist/handlers/index.js +188 -56
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/index.mjs +148 -16
- package/dist/handlers/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -3
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
- package/dist/StudioUI-7LIOKKXE.js.map +0 -1
- package/dist/StudioUI-CE7CEP63.mjs.map +0 -1
- package/dist/chunk-LEOQKJCL.js.map +0 -1
- package/dist/chunk-RDNC5ABF.mjs.map +0 -1
package/dist/handlers/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getAllThumbnailPaths,
|
|
3
3
|
getThumbnailPath
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-FDWPNRNZ.mjs";
|
|
5
5
|
|
|
6
6
|
// src/handlers/index.ts
|
|
7
|
-
import { NextResponse as
|
|
7
|
+
import { NextResponse as NextResponse5 } from "next/server";
|
|
8
8
|
|
|
9
9
|
// src/handlers/list.ts
|
|
10
10
|
import { NextResponse } from "next/server";
|
|
@@ -309,25 +309,58 @@ async function handleList(request) {
|
|
|
309
309
|
const items = [];
|
|
310
310
|
const seenFolders = /* @__PURE__ */ new Set();
|
|
311
311
|
const metaKeys = fileEntries.map(([key]) => key);
|
|
312
|
+
const isInsideImagesFolder = relativePath === "images" || relativePath.startsWith("images/");
|
|
312
313
|
const absoluteDir = path5.join(process.cwd(), requestedPath);
|
|
313
314
|
try {
|
|
314
315
|
const dirEntries = await fs4.readdir(absoluteDir, { withFileTypes: true });
|
|
315
316
|
for (const entry of dirEntries) {
|
|
316
|
-
if (entry.
|
|
317
|
+
if (entry.name.startsWith(".")) continue;
|
|
318
|
+
if (entry.isDirectory()) {
|
|
317
319
|
if (!seenFolders.has(entry.name)) {
|
|
318
320
|
seenFolders.add(entry.name);
|
|
319
|
-
const
|
|
321
|
+
const isImagesFolder = entry.name === "images" && !relativePath;
|
|
322
|
+
const folderPath = relativePath ? `public/${relativePath}/${entry.name}` : `public/${entry.name}`;
|
|
320
323
|
let fileCount = 0;
|
|
321
|
-
|
|
322
|
-
|
|
324
|
+
if (isInsideImagesFolder || isImagesFolder) {
|
|
325
|
+
const subDir = path5.join(absoluteDir, entry.name);
|
|
326
|
+
try {
|
|
327
|
+
const subEntries = await fs4.readdir(subDir);
|
|
328
|
+
fileCount = subEntries.filter((f) => !f.startsWith(".")).length;
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
const folderPrefix = pathPrefix === "/" ? `/${entry.name}/` : `${pathPrefix}${entry.name}/`;
|
|
333
|
+
for (const k of metaKeys) {
|
|
334
|
+
if (k.startsWith(folderPrefix)) fileCount++;
|
|
335
|
+
}
|
|
323
336
|
}
|
|
324
337
|
items.push({
|
|
325
338
|
name: entry.name,
|
|
326
|
-
path:
|
|
339
|
+
path: folderPath,
|
|
327
340
|
type: "folder",
|
|
328
|
-
fileCount
|
|
341
|
+
fileCount,
|
|
342
|
+
isProtected: isImagesFolder || isInsideImagesFolder
|
|
329
343
|
});
|
|
330
344
|
}
|
|
345
|
+
} else if (isInsideImagesFolder) {
|
|
346
|
+
const filePath = relativePath ? `public/${relativePath}/${entry.name}` : `public/${entry.name}`;
|
|
347
|
+
const fullPath = path5.join(absoluteDir, entry.name);
|
|
348
|
+
let fileSize;
|
|
349
|
+
try {
|
|
350
|
+
const stats = await fs4.stat(fullPath);
|
|
351
|
+
fileSize = stats.size;
|
|
352
|
+
} catch {
|
|
353
|
+
}
|
|
354
|
+
const isImage = isImageFile(entry.name);
|
|
355
|
+
items.push({
|
|
356
|
+
name: entry.name,
|
|
357
|
+
path: filePath,
|
|
358
|
+
type: "file",
|
|
359
|
+
size: fileSize,
|
|
360
|
+
thumbnail: isImage ? `/${relativePath}/${entry.name}` : void 0,
|
|
361
|
+
hasThumbnail: false,
|
|
362
|
+
isProtected: true
|
|
363
|
+
});
|
|
331
364
|
}
|
|
332
365
|
}
|
|
333
366
|
} catch {
|
|
@@ -354,7 +387,8 @@ async function handleList(request) {
|
|
|
354
387
|
name: folderName,
|
|
355
388
|
path: relativePath ? `public/${relativePath}/${folderName}` : `public/${folderName}`,
|
|
356
389
|
type: "folder",
|
|
357
|
-
fileCount
|
|
390
|
+
fileCount,
|
|
391
|
+
isProtected: isInsideImagesFolder
|
|
358
392
|
});
|
|
359
393
|
}
|
|
360
394
|
} else {
|
|
@@ -413,6 +447,7 @@ async function handleList(request) {
|
|
|
413
447
|
cdnPushed: isPushedToCloud,
|
|
414
448
|
cdnBaseUrl: fileCdnUrl,
|
|
415
449
|
isRemote,
|
|
450
|
+
isProtected: isInsideImagesFolder,
|
|
416
451
|
dimensions: entry.w && entry.h ? { width: entry.w, height: entry.h } : void 0
|
|
417
452
|
});
|
|
418
453
|
}
|
|
@@ -1429,6 +1464,7 @@ async function handleProcessAllStream() {
|
|
|
1429
1464
|
}
|
|
1430
1465
|
|
|
1431
1466
|
// src/handlers/scan.ts
|
|
1467
|
+
import { NextResponse as NextResponse4 } from "next/server";
|
|
1432
1468
|
import { promises as fs7 } from "fs";
|
|
1433
1469
|
import path8 from "path";
|
|
1434
1470
|
import sharp3 from "sharp";
|
|
@@ -1444,11 +1480,12 @@ async function handleScanStream() {
|
|
|
1444
1480
|
};
|
|
1445
1481
|
try {
|
|
1446
1482
|
const meta = await loadMeta();
|
|
1447
|
-
const existingCount = Object.keys(meta).length;
|
|
1483
|
+
const existingCount = Object.keys(meta).filter((k) => !k.startsWith("_")).length;
|
|
1448
1484
|
const existingKeys = new Set(Object.keys(meta));
|
|
1449
1485
|
const added = [];
|
|
1450
1486
|
const renamed = [];
|
|
1451
1487
|
const errors = [];
|
|
1488
|
+
const orphanedFiles = [];
|
|
1452
1489
|
const allFiles = [];
|
|
1453
1490
|
async function scanDir(dir, relativePath = "") {
|
|
1454
1491
|
try {
|
|
@@ -1538,6 +1575,40 @@ async function handleScanStream() {
|
|
|
1538
1575
|
errors.push(relativePath);
|
|
1539
1576
|
}
|
|
1540
1577
|
}
|
|
1578
|
+
sendEvent({ type: "cleanup", message: "Checking for orphaned thumbnails..." });
|
|
1579
|
+
const expectedThumbnails = /* @__PURE__ */ new Set();
|
|
1580
|
+
const fileEntries = getFileEntries(meta);
|
|
1581
|
+
for (const [imageKey, entry] of fileEntries) {
|
|
1582
|
+
if (entry.c === void 0 && entry.p === 1) {
|
|
1583
|
+
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
1584
|
+
expectedThumbnails.add(thumbPath);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
async function findOrphans(dir, relativePath = "") {
|
|
1589
|
+
try {
|
|
1590
|
+
const entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
1591
|
+
for (const entry of entries) {
|
|
1592
|
+
if (entry.name.startsWith(".")) continue;
|
|
1593
|
+
const fullPath = path8.join(dir, entry.name);
|
|
1594
|
+
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
1595
|
+
if (entry.isDirectory()) {
|
|
1596
|
+
await findOrphans(fullPath, relPath);
|
|
1597
|
+
} else if (isImageFile(entry.name)) {
|
|
1598
|
+
const publicPath = `/images/${relPath}`;
|
|
1599
|
+
if (!expectedThumbnails.has(publicPath)) {
|
|
1600
|
+
orphanedFiles.push(publicPath);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
} catch {
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
const imagesDir = path8.join(process.cwd(), "public", "images");
|
|
1608
|
+
try {
|
|
1609
|
+
await findOrphans(imagesDir);
|
|
1610
|
+
} catch {
|
|
1611
|
+
}
|
|
1541
1612
|
await saveMeta(meta);
|
|
1542
1613
|
sendEvent({
|
|
1543
1614
|
type: "complete",
|
|
@@ -1545,7 +1616,8 @@ async function handleScanStream() {
|
|
|
1545
1616
|
added: added.length,
|
|
1546
1617
|
renamed: renamed.length,
|
|
1547
1618
|
errors: errors.length,
|
|
1548
|
-
renamedFiles: renamed
|
|
1619
|
+
renamedFiles: renamed,
|
|
1620
|
+
orphanedFiles: orphanedFiles.length > 0 ? orphanedFiles : void 0
|
|
1549
1621
|
});
|
|
1550
1622
|
} catch (error) {
|
|
1551
1623
|
console.error("Scan failed:", error);
|
|
@@ -1563,6 +1635,63 @@ async function handleScanStream() {
|
|
|
1563
1635
|
}
|
|
1564
1636
|
});
|
|
1565
1637
|
}
|
|
1638
|
+
async function handleDeleteOrphans(request) {
|
|
1639
|
+
try {
|
|
1640
|
+
const { paths } = await request.json();
|
|
1641
|
+
if (!paths || !Array.isArray(paths) || paths.length === 0) {
|
|
1642
|
+
return NextResponse4.json({ error: "No paths provided" }, { status: 400 });
|
|
1643
|
+
}
|
|
1644
|
+
const deleted = [];
|
|
1645
|
+
const errors = [];
|
|
1646
|
+
for (const orphanPath of paths) {
|
|
1647
|
+
if (!orphanPath.startsWith("/images/")) {
|
|
1648
|
+
errors.push(`Invalid path: ${orphanPath}`);
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
const fullPath = path8.join(process.cwd(), "public", orphanPath);
|
|
1652
|
+
try {
|
|
1653
|
+
await fs7.unlink(fullPath);
|
|
1654
|
+
deleted.push(orphanPath);
|
|
1655
|
+
} catch (err) {
|
|
1656
|
+
console.error(`Failed to delete ${orphanPath}:`, err);
|
|
1657
|
+
errors.push(orphanPath);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
const imagesDir = path8.join(process.cwd(), "public", "images");
|
|
1661
|
+
async function removeEmptyDirs(dir) {
|
|
1662
|
+
try {
|
|
1663
|
+
const entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
1664
|
+
let isEmpty = true;
|
|
1665
|
+
for (const entry of entries) {
|
|
1666
|
+
if (entry.isDirectory()) {
|
|
1667
|
+
const subDirEmpty = await removeEmptyDirs(path8.join(dir, entry.name));
|
|
1668
|
+
if (!subDirEmpty) isEmpty = false;
|
|
1669
|
+
} else {
|
|
1670
|
+
isEmpty = false;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
if (isEmpty && dir !== imagesDir) {
|
|
1674
|
+
await fs7.rmdir(dir);
|
|
1675
|
+
}
|
|
1676
|
+
return isEmpty;
|
|
1677
|
+
} catch {
|
|
1678
|
+
return true;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
try {
|
|
1682
|
+
await removeEmptyDirs(imagesDir);
|
|
1683
|
+
} catch {
|
|
1684
|
+
}
|
|
1685
|
+
return NextResponse4.json({
|
|
1686
|
+
success: true,
|
|
1687
|
+
deleted: deleted.length,
|
|
1688
|
+
errors: errors.length
|
|
1689
|
+
});
|
|
1690
|
+
} catch (error) {
|
|
1691
|
+
console.error("Failed to delete orphans:", error);
|
|
1692
|
+
return NextResponse4.json({ error: "Failed to delete orphaned files" }, { status: 500 });
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1566
1695
|
|
|
1567
1696
|
// src/handlers/import.ts
|
|
1568
1697
|
import sharp4 from "sharp";
|
|
@@ -1693,7 +1822,7 @@ async function handleUpdateCdns(request) {
|
|
|
1693
1822
|
// src/handlers/index.ts
|
|
1694
1823
|
async function GET(request) {
|
|
1695
1824
|
if (process.env.NODE_ENV !== "development") {
|
|
1696
|
-
return
|
|
1825
|
+
return NextResponse5.json({ error: "Not available in production" }, { status: 403 });
|
|
1697
1826
|
}
|
|
1698
1827
|
const pathname = request.nextUrl.pathname;
|
|
1699
1828
|
const route = pathname.replace(/^\/api\/studio\/?/, "");
|
|
@@ -1715,11 +1844,11 @@ async function GET(request) {
|
|
|
1715
1844
|
if (route === "cdns") {
|
|
1716
1845
|
return handleGetCdns();
|
|
1717
1846
|
}
|
|
1718
|
-
return
|
|
1847
|
+
return NextResponse5.json({ error: "Not found" }, { status: 404 });
|
|
1719
1848
|
}
|
|
1720
1849
|
async function POST(request) {
|
|
1721
1850
|
if (process.env.NODE_ENV !== "development") {
|
|
1722
|
-
return
|
|
1851
|
+
return NextResponse5.json({ error: "Not available in production" }, { status: 403 });
|
|
1723
1852
|
}
|
|
1724
1853
|
const pathname = request.nextUrl.pathname;
|
|
1725
1854
|
const route = pathname.replace(/^\/api\/studio\/?/, "");
|
|
@@ -1750,17 +1879,20 @@ async function POST(request) {
|
|
|
1750
1879
|
if (route === "scan") {
|
|
1751
1880
|
return handleScanStream();
|
|
1752
1881
|
}
|
|
1882
|
+
if (route === "delete-orphans") {
|
|
1883
|
+
return handleDeleteOrphans(request);
|
|
1884
|
+
}
|
|
1753
1885
|
if (route === "import") {
|
|
1754
1886
|
return handleImportUrls(request);
|
|
1755
1887
|
}
|
|
1756
1888
|
if (route === "cdns") {
|
|
1757
1889
|
return handleUpdateCdns(request);
|
|
1758
1890
|
}
|
|
1759
|
-
return
|
|
1891
|
+
return NextResponse5.json({ error: "Not found" }, { status: 404 });
|
|
1760
1892
|
}
|
|
1761
1893
|
async function DELETE(request) {
|
|
1762
1894
|
if (process.env.NODE_ENV !== "development") {
|
|
1763
|
-
return
|
|
1895
|
+
return NextResponse5.json({ error: "Not available in production" }, { status: 403 });
|
|
1764
1896
|
}
|
|
1765
1897
|
return handleDelete(request);
|
|
1766
1898
|
}
|