delete-all-uncropped.js
1 #!/usr/bin/env node 2 3 /** 4 * Delete All Uncropped Screenshots 5 * 6 * Deletes all uncropped screenshot versions to reclaim disk space. 7 * The assets stage will recapture these sites with the new thresholds, 8 * only storing uncropped versions when they differ significantly (> 5KB AND > 1%). 9 */ 10 11 import { readdir, stat, unlink } from 'fs/promises'; 12 import { join, dirname } from 'path'; 13 import { fileURLToPath } from 'url'; 14 15 const __filename = fileURLToPath(import.meta.url); 16 const __dirname = dirname(__filename); 17 18 const SCREENSHOTS_DIR = process.env.SCREENSHOT_BASE_PATH || join(__dirname, '..', 'screenshots'); 19 20 // Parse command line args 21 const dryRun = process.argv.includes('--dry-run'); 22 23 // Uncropped screenshots to delete 24 const UNCROPPED_FILES = [ 25 'desktop_above_uncropped.jpg', 26 'desktop_below_uncropped.jpg', 27 'mobile_above_uncropped.jpg', 28 ]; 29 30 /** 31 * Format bytes to human readable format 32 */ 33 function formatBytes(bytes) { 34 if (bytes === 0) return '0 Bytes'; 35 const k = 1024; 36 const sizes = ['Bytes', 'KB', 'MB', 'GB']; 37 const i = Math.floor(Math.log(bytes) / Math.log(k)); 38 return `${Math.round((bytes / Math.pow(k, i)) * 100) / 100} ${sizes[i]}`; 39 } 40 41 /** 42 * Get file size in bytes 43 */ 44 async function getFileSize(filePath) { 45 try { 46 const stats = await stat(filePath); 47 return stats.size; 48 } catch { 49 return 0; 50 } 51 } 52 53 async function main() { 54 console.log('š Scanning for uncropped screenshots to delete...\n'); 55 56 // Get all screenshot folders 57 let screenshotFolders; 58 try { 59 screenshotFolders = await readdir(SCREENSHOTS_DIR); 60 } catch (error) { 61 console.error('ā Error reading screenshots directory:', error.message); 62 process.exit(1); 63 } 64 65 console.log(`Found ${screenshotFolders.length} screenshot folders\n`); 66 67 const stats = { 68 foldersChecked: 0, 69 filesFound: 0, 70 spaceToFree: 0, 71 filesToDelete: [], 72 }; 73 74 // Check each folder 75 for (const folder of screenshotFolders) { 76 const folderPath = join(SCREENSHOTS_DIR, folder); 77 78 // Skip if not a directory 79 try { 80 const folderStats = await stat(folderPath); 81 if (!folderStats.isDirectory()) { 82 continue; 83 } 84 } catch { 85 continue; 86 } 87 88 stats.foldersChecked++; 89 90 // Check for each uncropped file 91 for (const filename of UNCROPPED_FILES) { 92 const filePath = join(folderPath, filename); 93 94 try { 95 await stat(filePath); 96 // File exists 97 const fileSize = await getFileSize(filePath); 98 stats.filesFound++; 99 stats.spaceToFree += fileSize; 100 stats.filesToDelete.push({ 101 path: filePath, 102 size: fileSize, 103 folder, 104 filename, 105 }); 106 } catch { 107 // File doesn't exist - skip 108 } 109 } 110 111 // Progress indicator every 100 folders 112 if (stats.foldersChecked % 100 === 0) { 113 console.log(` Checked ${stats.foldersChecked}/${screenshotFolders.length} folders...`); 114 } 115 } 116 117 console.log('\nš Scan Results:'); 118 console.log(` Folders checked: ${stats.foldersChecked}`); 119 console.log(` Uncropped files found: ${stats.filesFound}`); 120 console.log(`\nš¾ Space to be freed: ${formatBytes(stats.spaceToFree)}`); 121 122 if (stats.filesFound === 0) { 123 console.log('\nā No uncropped screenshots found!'); 124 return; 125 } 126 127 if (dryRun) { 128 console.log('\nš DRY RUN MODE - No files will be deleted\n'); 129 console.log('Sample files that would be deleted:'); 130 stats.filesToDelete.slice(0, 10).forEach(file => { 131 console.log(` - ${file.folder}/${file.filename} (${formatBytes(file.size)})`); 132 }); 133 if (stats.filesToDelete.length > 10) { 134 console.log(` ... and ${stats.filesToDelete.length - 10} more`); 135 } 136 console.log('\nRun without --dry-run to actually delete these files'); 137 } else { 138 console.log('\nšļø Deleting uncropped screenshots...'); 139 140 let deleted = 0; 141 let deleteErrors = 0; 142 143 for (const file of stats.filesToDelete) { 144 try { 145 await unlink(file.path); 146 deleted++; 147 if (deleted % 100 === 0) { 148 console.log(` Deleted ${deleted}/${stats.filesToDelete.length} files...`); 149 } 150 } catch (error) { 151 deleteErrors++; 152 console.error(` ā Error deleting ${file.path}: ${error.message}`); 153 } 154 } 155 156 console.log('\nā Cleanup complete!'); 157 console.log(` Files deleted: ${deleted}`); 158 console.log(` Errors: ${deleteErrors}`); 159 console.log(` Space freed: ${formatBytes(stats.spaceToFree)}`); 160 console.log( 161 '\nš” Run "npm run assets" to recapture sites with missing screenshots (uncropped versions will only be saved if they differ significantly)' 162 ); 163 } 164 } 165 166 main().catch(error => { 167 console.error('ā Fatal error:', error); 168 process.exit(1); 169 });