Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@
"react-dom": "^19.2.3",
"react-icons": "^5.5.0"
},
"devDependencies": {
"sharp": "^0.34.5"
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"scripts": {
"dev": "next dev",
"prebuild": "npm run optimize-images",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"optimize-images": "node scripts/optimize-images.js"
}
}
}
152 changes: 152 additions & 0 deletions scripts/optimize-images.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/**
* Image Optimization Script
*
* Uses Sharp to compress PNG images and generate WebP versions.
* Run with: npm run optimize-images
*/

const sharp = require('sharp');
const fs = require('fs');
const path = require('path');

const ASSETS_DIR = path.join(__dirname, '..', 'app', 'assets');
const PNG_QUALITY = 80;
const WEBP_QUALITY = 80;

// Images to optimize (large PNG files)
const IMAGES_TO_OPTIMIZE = [
'resonate_app.png',
'Vector.png',
'Group.png',
'createrooms.png',
'roomscreen.png',
'pairchat.png',
'chatscreen.png',
'aossie_logo.png',
'PlayStore.png',
];
Comment thread
Rozerxshashank marked this conversation as resolved.
Outdated

async function getFileSize(filePath) {
try {
const stats = await fs.promises.stat(filePath);
return stats.size;
} catch (error) {
// File doesn't exist or is inaccessible
return 0;
}
}

async function fileExists(filePath) {
try {
await fs.promises.access(filePath, fs.constants.F_OK);
return true;
} catch {
return false;
}
}

function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}

async function optimizeImage(filename) {
const inputPath = path.join(ASSETS_DIR, filename);
const tempPath = path.join(ASSETS_DIR, `temp_${filename}`);
const webpPath = path.join(ASSETS_DIR, filename.replace('.png', '.webp'));

// Check if file exists
if (!await fileExists(inputPath)) {
console.log(`Skipping ${filename} - file not found`);
return null;
}

const originalSize = await getFileSize(inputPath);

try {
// Compress PNG
await sharp(inputPath)
.png({
quality: PNG_QUALITY,
compressionLevel: 9
})
.toFile(tempPath);

// Atomically replace original with compressed version
// rename() overwrites the destination if it exists (atomic on same filesystem)
await fs.promises.rename(tempPath, inputPath);

const compressedSize = await getFileSize(inputPath);

// Generate WebP version
await sharp(inputPath)
.webp({ quality: WEBP_QUALITY })
.toFile(webpPath);

const webpSize = await getFileSize(webpPath);

return {
filename,
originalSize,
compressedSize,
webpSize,
savings: originalSize - compressedSize,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
savingsPercent: originalSize > 0
? ((originalSize - compressedSize) / originalSize * 100).toFixed(1)
: '0.0'
};
} catch (error) {
console.error(`Error optimizing ${filename}:`, error.message);
// Clean up temp file if it exists
if (await fileExists(tempPath)) {
await fs.promises.unlink(tempPath);
}
Comment on lines +149 to +154
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard cleanup unlink inside catch to preserve per-file fault isolation.

At Line 153, if unlink(tempPath) fails inside the catch block, optimizeImage() can still reject and interrupt the batch.

🧯 Suggested hardening
     } catch (error) {
         console.error(`Error optimizing ${filename}:`, error.message);
         // Clean up temp file if it exists
         if (await fileExists(tempPath)) {
-            await fs.promises.unlink(tempPath);
+            try {
+                await fs.promises.unlink(tempPath);
+            } catch (cleanupError) {
+                console.error(`Failed to clean temp file for ${filename}:`, cleanupError.message);
+            }
         }
         return null;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/optimize-images.js` around lines 149 - 154, The catch block in
optimizeImage currently calls await fs.promises.unlink(tempPath) directly which
can throw and break per-file fault isolation; wrap the cleanup of tempPath in
its own safe guard (e.g., check fileExists(tempPath) then attempt unlink inside
a nested try/catch) so any error during fs.promises.unlink(tempPath) is
swallowed or logged but does not rethrow, ensuring optimizeImage continues to
resolve/reject based on the original optimization error only.

return null;
}
}

async function main() {
console.log('\nImage Optimization Script\n');
console.log('='.repeat(60));

const results = [];
let totalOriginal = 0;
let totalCompressed = 0;

for (const filename of IMAGES_TO_OPTIMIZE) {
process.stdout.write(`Processing ${filename}... `);
const result = await optimizeImage(filename);

if (result) {
results.push(result);
totalOriginal += result.originalSize;
totalCompressed += result.compressedSize;
console.log(`Done - Saved ${result.savingsPercent}%`);
}
}

console.log('\n' + '='.repeat(60));
console.log('\nOptimization Results:\n');
console.log('| Image | Original | Compressed | WebP | Savings |');
console.log('|-------|----------|------------|------|---------|');

for (const r of results) {
console.log(`| ${r.filename.substring(0, 20).padEnd(20)} | ${formatBytes(r.originalSize).padEnd(8)} | ${formatBytes(r.compressedSize).padEnd(10)} | ${formatBytes(r.webpSize).padEnd(6)} | ${r.savingsPercent}% |`);
}

console.log('\n' + '='.repeat(60));

// Prevent division by zero if no images were processed
if (totalOriginal === 0) {
console.log('\nNo images were processed.\n');
return;
}

console.log(`\nTotal: ${formatBytes(totalOriginal)} -> ${formatBytes(totalCompressed)}`);
console.log(`Saved: ${formatBytes(totalOriginal - totalCompressed)} (${((totalOriginal - totalCompressed) / totalOriginal * 100).toFixed(1)}%)\n`);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

main().catch(console.error);