@gallop.software/studio 0.1.24 → 0.1.26

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/handlers.js CHANGED
@@ -22,8 +22,8 @@ async function GET(request) {
22
22
  if (route === "scan") {
23
23
  return handleScan();
24
24
  }
25
- if (route === "count-unprocessed") {
26
- return handleCountUnprocessed();
25
+ if (route === "count-images") {
26
+ return handleCountImages();
27
27
  }
28
28
  return _server.NextResponse.json({ error: "Not found" }, { status: 404 });
29
29
  }
@@ -46,7 +46,7 @@ async function POST(request) {
46
46
  return handleReprocess(request);
47
47
  }
48
48
  if (route === "process-all") {
49
- return handleProcessAll();
49
+ return handleProcessAllStream();
50
50
  }
51
51
  return _server.NextResponse.json({ error: "Not found" }, { status: 404 });
52
52
  }
@@ -494,10 +494,9 @@ async function handleReprocess(request) {
494
494
  return _server.NextResponse.json({ error: "Failed to reprocess images" }, { status: 500 });
495
495
  }
496
496
  }
497
- async function handleCountUnprocessed() {
497
+ async function handleCountImages() {
498
498
  try {
499
- const meta = await loadMeta();
500
- const unprocessedImages = [];
499
+ const allImages = [];
501
500
  async function scanPublicFolder(dir, relativePath = "") {
502
501
  try {
503
502
  const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
@@ -509,9 +508,7 @@ async function handleCountUnprocessed() {
509
508
  if (entry.isDirectory()) {
510
509
  await scanPublicFolder(fullPath, relPath);
511
510
  } else if (isImageFile(entry.name)) {
512
- if (!meta.images[relPath]) {
513
- unprocessedImages.push(relPath);
514
- }
511
+ allImages.push(relPath);
515
512
  }
516
513
  }
517
514
  } catch (e7) {
@@ -520,163 +517,192 @@ async function handleCountUnprocessed() {
520
517
  const publicDir = _path2.default.join(process.cwd(), "public");
521
518
  await scanPublicFolder(publicDir);
522
519
  return _server.NextResponse.json({
523
- count: unprocessedImages.length,
524
- images: unprocessedImages
520
+ count: allImages.length,
521
+ images: allImages
525
522
  });
526
523
  } catch (error) {
527
- console.error("Failed to count unprocessed images:", error);
528
- return _server.NextResponse.json({ error: "Failed to count unprocessed images" }, { status: 500 });
524
+ console.error("Failed to count images:", error);
525
+ return _server.NextResponse.json({ error: "Failed to count images" }, { status: 500 });
529
526
  }
530
527
  }
531
- async function handleProcessAll() {
532
- try {
533
- const meta = await loadMeta();
534
- const processed = [];
535
- const errors = [];
536
- const orphansRemoved = [];
537
- const unprocessedImages = [];
538
- async function scanPublicFolder(dir, relativePath = "") {
528
+ async function handleProcessAllStream() {
529
+ const encoder = new TextEncoder();
530
+ const stream = new ReadableStream({
531
+ async start(controller) {
532
+ const sendEvent = (data) => {
533
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
534
+
535
+ `));
536
+ };
539
537
  try {
540
- const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
541
- for (const entry of entries) {
542
- if (entry.name.startsWith(".")) continue;
543
- const fullPath = _path2.default.join(dir, entry.name);
544
- const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
545
- if (relPath === "images" || relPath.startsWith("images/")) continue;
546
- if (entry.isDirectory()) {
547
- await scanPublicFolder(fullPath, relPath);
548
- } else if (isImageFile(entry.name)) {
549
- if (!meta.images[relPath]) {
550
- unprocessedImages.push({ key: relPath, fullPath });
538
+ const meta = await loadMeta();
539
+ const processed = [];
540
+ const errors = [];
541
+ const orphansRemoved = [];
542
+ const allImages = [];
543
+ async function scanPublicFolder(dir, relativePath = "") {
544
+ try {
545
+ const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
546
+ for (const entry of entries) {
547
+ if (entry.name.startsWith(".")) continue;
548
+ const fullPath = _path2.default.join(dir, entry.name);
549
+ const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
550
+ if (relPath === "images" || relPath.startsWith("images/")) continue;
551
+ if (entry.isDirectory()) {
552
+ await scanPublicFolder(fullPath, relPath);
553
+ } else if (isImageFile(entry.name)) {
554
+ allImages.push({ key: relPath, fullPath });
555
+ }
551
556
  }
557
+ } catch (e8) {
552
558
  }
553
559
  }
554
- } catch (e8) {
555
- }
556
- }
557
- const publicDir = _path2.default.join(process.cwd(), "public");
558
- await scanPublicFolder(publicDir);
559
- for (const { key, fullPath } of unprocessedImages) {
560
- try {
561
- const buffer = await _fs.promises.readFile(fullPath);
562
- const ext = _path2.default.extname(key).toLowerCase();
563
- const isSvg = ext === ".svg";
564
- if (isSvg) {
565
- const imageDir = _path2.default.dirname(key);
566
- const imagesPath = _path2.default.join(process.cwd(), "public", "images", imageDir === "." ? "" : imageDir);
567
- await _fs.promises.mkdir(imagesPath, { recursive: true });
568
- const fileName = _path2.default.basename(key);
569
- const destPath = _path2.default.join(imagesPath, fileName);
570
- await _fs.promises.writeFile(destPath, buffer);
571
- const sizePath = `/images/${imageDir === "." ? "" : imageDir + "/"}${fileName}`;
572
- meta.images[key] = {
573
- original: {
574
- path: `/${key}`,
575
- width: 0,
576
- height: 0,
577
- fileSize: buffer.length
578
- },
579
- sizes: {
580
- full: { path: sizePath, width: 0, height: 0 },
581
- large: { path: sizePath, width: 0, height: 0 },
582
- medium: { path: sizePath, width: 0, height: 0 },
583
- small: { path: sizePath, width: 0, height: 0 }
584
- },
585
- blurhash: "",
586
- dominantColor: "#888888",
587
- cdn: null
588
- };
589
- } else {
590
- const dummyEntry = {
591
- original: {
592
- path: `/${key}`,
593
- width: 0,
594
- height: 0,
595
- fileSize: buffer.length
596
- },
597
- sizes: {
598
- full: { path: "", width: 0, height: 0 },
599
- large: { path: "", width: 0, height: 0 },
600
- medium: { path: "", width: 0, height: 0 },
601
- small: { path: "", width: 0, height: 0 }
602
- },
603
- blurhash: "",
604
- dominantColor: "#888888",
605
- cdn: null
606
- };
607
- const processedEntry = await processImage(buffer, dummyEntry, key);
608
- meta.images[key] = processedEntry;
560
+ const publicDir = _path2.default.join(process.cwd(), "public");
561
+ await scanPublicFolder(publicDir);
562
+ const total = allImages.length;
563
+ sendEvent({ type: "start", total });
564
+ for (let i = 0; i < allImages.length; i++) {
565
+ const { key, fullPath } = allImages[i];
566
+ sendEvent({
567
+ type: "progress",
568
+ current: i + 1,
569
+ total,
570
+ percent: Math.round((i + 1) / total * 100),
571
+ currentFile: key
572
+ });
573
+ try {
574
+ const buffer = await _fs.promises.readFile(fullPath);
575
+ const ext = _path2.default.extname(key).toLowerCase();
576
+ const isSvg = ext === ".svg";
577
+ if (isSvg) {
578
+ const imageDir = _path2.default.dirname(key);
579
+ const imagesPath = _path2.default.join(process.cwd(), "public", "images", imageDir === "." ? "" : imageDir);
580
+ await _fs.promises.mkdir(imagesPath, { recursive: true });
581
+ const fileName = _path2.default.basename(key);
582
+ const destPath = _path2.default.join(imagesPath, fileName);
583
+ await _fs.promises.writeFile(destPath, buffer);
584
+ const sizePath = `/images/${imageDir === "." ? "" : imageDir + "/"}${fileName}`;
585
+ meta.images[key] = {
586
+ original: {
587
+ path: `/${key}`,
588
+ width: 0,
589
+ height: 0,
590
+ fileSize: buffer.length
591
+ },
592
+ sizes: {
593
+ full: { path: sizePath, width: 0, height: 0 },
594
+ large: { path: sizePath, width: 0, height: 0 },
595
+ medium: { path: sizePath, width: 0, height: 0 },
596
+ small: { path: sizePath, width: 0, height: 0 }
597
+ },
598
+ blurhash: "",
599
+ dominantColor: "#888888",
600
+ cdn: null
601
+ };
602
+ } else {
603
+ const existingEntry = meta.images[key];
604
+ const baseEntry = existingEntry || {
605
+ original: {
606
+ path: `/${key}`,
607
+ width: 0,
608
+ height: 0,
609
+ fileSize: buffer.length
610
+ },
611
+ sizes: {
612
+ full: { path: "", width: 0, height: 0 },
613
+ large: { path: "", width: 0, height: 0 },
614
+ medium: { path: "", width: 0, height: 0 },
615
+ small: { path: "", width: 0, height: 0 }
616
+ },
617
+ blurhash: "",
618
+ dominantColor: "#888888",
619
+ cdn: null
620
+ };
621
+ const processedEntry = await processImage(buffer, baseEntry, key);
622
+ meta.images[key] = processedEntry;
623
+ }
624
+ processed.push(key);
625
+ } catch (error) {
626
+ console.error(`Failed to process ${key}:`, error);
627
+ errors.push(key);
628
+ }
609
629
  }
610
- processed.push(key);
611
- } catch (error) {
612
- console.error(`Failed to process ${key}:`, error);
613
- errors.push(key);
614
- }
615
- }
616
- const trackedPaths = /* @__PURE__ */ new Set();
617
- for (const entry of Object.values(meta.images)) {
618
- for (const sizeData of Object.values(entry.sizes)) {
619
- trackedPaths.add(sizeData.path);
620
- }
621
- }
622
- async function findOrphans(dir, relativePath = "") {
623
- try {
624
- const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
625
- for (const entry of entries) {
626
- if (entry.name.startsWith(".")) continue;
627
- const fullPath = _path2.default.join(dir, entry.name);
628
- const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
629
- if (entry.isDirectory()) {
630
- await findOrphans(fullPath, relPath);
631
- } else if (isImageFile(entry.name)) {
632
- const publicPath = `/images/${relPath}`;
633
- if (!trackedPaths.has(publicPath)) {
634
- try {
635
- await _fs.promises.unlink(fullPath);
636
- orphansRemoved.push(publicPath);
637
- } catch (err) {
638
- console.error(`Failed to remove orphan ${publicPath}:`, err);
630
+ sendEvent({ type: "cleanup", message: "Removing orphaned thumbnails..." });
631
+ const trackedPaths = /* @__PURE__ */ new Set();
632
+ for (const entry of Object.values(meta.images)) {
633
+ for (const sizeData of Object.values(entry.sizes)) {
634
+ trackedPaths.add(sizeData.path);
635
+ }
636
+ }
637
+ async function findOrphans(dir, relativePath = "") {
638
+ try {
639
+ const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
640
+ for (const entry of entries) {
641
+ if (entry.name.startsWith(".")) continue;
642
+ const fullPath = _path2.default.join(dir, entry.name);
643
+ const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
644
+ if (entry.isDirectory()) {
645
+ await findOrphans(fullPath, relPath);
646
+ } else if (isImageFile(entry.name)) {
647
+ const publicPath = `/images/${relPath}`;
648
+ if (!trackedPaths.has(publicPath)) {
649
+ try {
650
+ await _fs.promises.unlink(fullPath);
651
+ orphansRemoved.push(publicPath);
652
+ } catch (err) {
653
+ console.error(`Failed to remove orphan ${publicPath}:`, err);
654
+ }
655
+ }
639
656
  }
640
657
  }
658
+ } catch (e9) {
641
659
  }
642
660
  }
643
- } catch (e9) {
644
- }
645
- }
646
- const imagesDir = _path2.default.join(process.cwd(), "public", "images");
647
- await findOrphans(imagesDir);
648
- async function removeEmptyDirs(dir) {
649
- try {
650
- const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
651
- let isEmpty = true;
652
- for (const entry of entries) {
653
- if (entry.isDirectory()) {
654
- const subDirEmpty = await removeEmptyDirs(_path2.default.join(dir, entry.name));
655
- if (!subDirEmpty) isEmpty = false;
656
- } else {
657
- isEmpty = false;
661
+ const imagesDir = _path2.default.join(process.cwd(), "public", "images");
662
+ await findOrphans(imagesDir);
663
+ async function removeEmptyDirs(dir) {
664
+ try {
665
+ const entries = await _fs.promises.readdir(dir, { withFileTypes: true });
666
+ let isEmpty = true;
667
+ for (const entry of entries) {
668
+ if (entry.isDirectory()) {
669
+ const subDirEmpty = await removeEmptyDirs(_path2.default.join(dir, entry.name));
670
+ if (!subDirEmpty) isEmpty = false;
671
+ } else {
672
+ isEmpty = false;
673
+ }
674
+ }
675
+ if (isEmpty && dir !== imagesDir) {
676
+ await _fs.promises.rmdir(dir);
677
+ }
678
+ return isEmpty;
679
+ } catch (e10) {
680
+ return true;
658
681
  }
659
682
  }
660
- if (isEmpty && dir !== imagesDir) {
661
- await _fs.promises.rmdir(dir);
662
- }
663
- return isEmpty;
664
- } catch (e10) {
665
- return true;
683
+ await removeEmptyDirs(imagesDir);
684
+ await saveMeta(meta);
685
+ sendEvent({
686
+ type: "complete",
687
+ processed: processed.length,
688
+ orphansRemoved: orphansRemoved.length,
689
+ errors: errors.length
690
+ });
691
+ } catch (error) {
692
+ console.error("Failed to process all:", error);
693
+ sendEvent({ type: "error", message: "Failed to process images" });
694
+ } finally {
695
+ controller.close();
666
696
  }
667
697
  }
668
- await removeEmptyDirs(imagesDir);
669
- await saveMeta(meta);
670
- return _server.NextResponse.json({
671
- success: true,
672
- processed,
673
- orphansRemoved,
674
- errors: errors.length > 0 ? errors : void 0
675
- });
676
- } catch (error) {
677
- console.error("Failed to process all:", error);
678
- return _server.NextResponse.json({ error: "Failed to process all images" }, { status: 500 });
679
- }
698
+ });
699
+ return new Response(stream, {
700
+ headers: {
701
+ "Content-Type": "text/event-stream",
702
+ "Cache-Control": "no-cache",
703
+ "Connection": "keep-alive"
704
+ }
705
+ });
680
706
  }
681
707
  async function loadMeta() {
682
708
  const metaPath = _path2.default.join(process.cwd(), "_data", "_meta.json");