Skip to content

Chore: CI/CD assets tests#2572

Merged
TorstenDittmann merged 9 commits intomainfrom
chore-assets-tests
Nov 7, 2025
Merged

Chore: CI/CD assets tests#2572
TorstenDittmann merged 9 commits intomainfrom
chore-assets-tests

Conversation

@Meldiron
Copy link
Contributor

@Meldiron Meldiron commented Nov 6, 2025

What does this PR do?

Adds assetstest for CI/CD, ensuring all files are optmal

Test Plan

CI/CD should pass

Related PRs and Issues

x

Have you read the Contributing Guidelines on issues?

Yes

Summary by CodeRabbit

  • Tests

    • CI adds an assets job that runs asset optimization, verifies no unintended changes, and fails with a clear diff and message if optimizations introduce modifications; emits a clear success notice when optimization passes.
  • Chores

    • Asset optimizer improved: enforces meaningful reductions (≥20%), skips non-beneficial edits, provides per-file success logs, safer write/cleanup behavior, and ignores temporary optimized files.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 6, 2025

Walkthrough

Adds an "assets" job to the CI workflow that checks out the PR, sets up Bun, installs dependencies, runs the asset optimization script, and fails if optimization produces uncommitted changes. The optimize-assets.js script was reworked to use disk-based temporary files with a .optimized suffix, obtain image metadata with CI-strict error handling, enforce minimum size/dimension checks and a 20% minimum size-reduction threshold before replacing originals, perform atomic replace of originals from optimized files, log per-file optimization results, and stop early on CI after the first successful optimization. .gitignore now ignores assets/**/*.optimized.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review disk-based temp file lifecycle: creation, size comparison, atomic replace, and cleanup on errors.
  • Validate the 20% reduction threshold and percentage calculation, including edge cases (zero sizes, rounding).
  • Verify CI-specific behavior: metadata errors causing CI failure, skipping or stopping behavior in CI, and early termination after first successful optimization.
  • Inspect error handling around file I/O and cleanup paths to avoid leaving .optimized artifacts.
  • Confirm workflow git-diff detection and the .gitignore addition (assets/**/*.optimized) correctly suppresses generated files.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Chore: CI/CD assets tests' directly reflects the main change: adding asset optimization validation to the CI/CD pipeline.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore-assets-tests

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd1bcd4 and f5bcc41.

📒 Files selected for processing (1)
  • scripts/optimize-assets.js (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build
  • GitHub Check: assets
  • GitHub Check: tests
🔇 Additional comments (9)
scripts/optimize-assets.js (9)

62-64: LGTM: Helpful startup message.

The informational message sets clear expectations for developers running the script. The runtime estimate is reasonable given the processing involved.


73-84: LGTM: Appropriate CI-strict error handling.

The metadata error handling correctly distinguishes between CI and local runs. Failing fast in CI ensures problematic files are caught, while local runs can continue for developer convenience.


86-89: LGTM: Dimension guards correctly scoped to CI.

The dimension checks now correctly wrap both width and height under the Bun.env.CI condition, addressing the previous review feedback. This ensures oversized images are caught in CI while allowing local development flexibility.


95-103: LGTM: Correctly addresses lazy-loaded size property.

The implementation properly forces the lazy-loaded .size property to populate by calling arrayBuffer() before accessing it, as documented in the Bun.file() API. The optimized file contents are correctly stored in memory for the atomic rewrite step.


105-109: LGTM: Proper size reduction validation.

The check ensures only files with actual size reductions proceed, preventing unnecessary file replacements. This also implicitly guards against division by zero in the subsequent percentage calculation.


111-115: LGTM: Intentional 20% minimum threshold.

The 20% minimum size-reduction threshold is intentionally high to avoid CI noise from minor optimizations, as noted in your previous comments. This design choice ensures only meaningful optimizations trigger CI failures.


117-129: LGTM: Substantially improved atomic rewrite with error handling.

The atomic rewrite mechanism is much safer now. By buffering the optimized contents in memory before writing to the original path and wrapping the operation in proper error handling with cleanup, you've addressed the previous critical concern about potential file loss.


131-132: LGTM: Clear success logging.

The success message provides helpful feedback with the relative path and optimization percentage, making it easy to see which files were optimized and by how much.


134-139: LGTM: CI early exit correctly implemented.

The break statement now properly terminates the loop after the first optimization in CI, addressing the previous critical feedback. This optimization prevents long CI runs while still catching unoptimized assets.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
scripts/optimize-assets.js (1)

69-104: Consider adding cleanup for temporary files on error.

If the script encounters an error during optimization, temporary .optimized files could be left behind in the filesystem. Consider wrapping the main loop in error handling to ensure cleanup.

 async function main() {
+    const tempFiles = [];
+    try {
         for (const file of walk_directory(join(__dirname, '../static'))) {
             const relative_path = get_relative_path(file);
             const is_animated = file.endsWith('.gif');
             if (!is_image(file)) continue;
             if (exceptions.some((exception) => relative_path.startsWith(exception))) continue;
     
             const image = sharp(file, {
                 animated: is_animated
             });
             const meta = await image.metadata();
             const buffer = await image[meta.format](config[meta.format])
                 .resize(resize_config)
                 .toBuffer();
     
             await sharp(buffer).toFile(file + '.optimized');
+            tempFiles.push(file + '.optimized');
     
             const size_before = Bun.file(file).size;
             const size_after = Bun.file(file + '.optimized').size;
     
             const size_diff = size_before - size_after;
             const size_diff_percent = size_diff / size_before;
     
             if (size_diff <= 0 || size_diff_percent < 0.2) {
                 await Bun.file(file + '.optimized').delete();
+                tempFiles.pop();
                 continue;
             }
     
             await Bun.file(file).delete();
             await Bun.write(file, Bun.file(file + '.optimized'));
             await Bun.file(file + '.optimized').delete();
+            tempFiles.pop();
     
             const diff_verbose = Math.round(size_diff_percent * 100);
             console.log(`✅ ${relative_path} has been optimized (-${diff_verbose}%)`);
         }
+    } catch (error) {
+        // Clean up any remaining temp files
+        for (const tempFile of tempFiles) {
+            try {
+                await Bun.file(tempFile).delete();
+            } catch {}
+        }
+        throw error;
+    }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e073cc and 583b545.

📒 Files selected for processing (2)
  • .github/workflows/tests.yml (1 hunks)
  • scripts/optimize-assets.js (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: tests
🔇 Additional comments (3)
scripts/optimize-assets.js (1)

101-102: LGTM!

Clear and informative logging that shows the optimization percentage. The use of Math.round for display formatting is appropriate.

.github/workflows/tests.yml (2)

80-103: Well-structured asset optimization validation job.

The workflow correctly validates that assets are optimized by running the optimization script and checking for uncommitted changes. The error messaging is clear and actionable.

One consideration: this job's reliability depends on the safety of the optimization script. Given the file loss risk identified in scripts/optimize-assets.js (lines 97-99), ensure that script is hardened before relying on it in CI to avoid potential asset corruption.


94-102: Clear validation logic with helpful error messaging.

The use of git diff --exit-code correctly detects uncommitted changes, and the error output provides both the diff and clear instructions for remediation.

@Meldiron Meldiron marked this pull request as draft November 6, 2025 14:19
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
scripts/optimize-assets.js (3)

106-107: CRITICAL: Lazy-loaded .size property still not fixed.

The issue flagged in the past review remains unresolved. Bun.file().size is lazy-loaded and will return invalid values until the file contents are read. Both size measurements will be incorrect, causing the optimization logic to fail.

Apply the fix from the past review:

+        // Read files to ensure .size is loaded
+        const originalFile = Bun.file(file);
+        const optimizedFile = Bun.file(file + '.optimized');
+        await originalFile.arrayBuffer();
+        await optimizedFile.arrayBuffer();
+        
-        const size_before = Bun.file(file).size;
-        const size_after = Bun.file(file + '.optimized').size;
+        const size_before = originalFile.size;
+        const size_after = optimizedFile.size;

109-110: Edge case: Division by zero still unhandled.

The issue flagged in the past review remains unresolved. If size_before is 0 (corrupted or empty file), line 110 will cause division by zero.

Apply the guard from the past review:

 const size_diff = size_before - size_after;
+if (size_before === 0) {
+    await Bun.file(file + '.optimized').delete();
+    continue;
+}
 const size_diff_percent = size_diff / size_before;

116-118: CRITICAL: Non-atomic file replacement still not fixed.

The issue flagged in the past review remains unresolved. The delete-write-delete sequence is non-atomic and creates a window where the original file can be permanently lost if line 117 fails after line 116 deletes the original. This could happen due to disk space, permissions, or I/O errors.

Apply the atomic replacement from the past review:

-        await Bun.file(file).delete();
-        await Bun.write(file, Bun.file(file + '.optimized'));
-        await Bun.file(file + '.optimized').delete();
+        try {
+            const optimizedContent = await Bun.file(file + '.optimized').arrayBuffer();
+            await Bun.write(file, optimizedContent);
+            await Bun.file(file + '.optimized').delete();
+        } catch (error) {
+            // Clean up temp file if replacement fails
+            try {
+                await Bun.file(file + '.optimized').delete();
+            } catch {}
+            throw new Error(`Failed to replace ${relative_path}: ${error.message}`);
+        }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 583b545 and 6eddb8e.

📒 Files selected for processing (2)
  • .github/workflows/tests.yml (1 hunks)
  • scripts/optimize-assets.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/tests.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: tests
🔇 Additional comments (5)
scripts/optimize-assets.js (5)

29-33: LGTM - GIF optimization disabled.

The GIF configuration is properly commented out. This appears intentional and the implementation is clean.


83-86: LGTM - Sharp initialization with proper safeguards.

Good addition of limitInputPixels: 1920 * 1920 to prevent excessive memory usage with large images, and the animated flag is properly preserved.


88-98: LGTM - Robust metadata error handling.

Excellent addition of try/catch around metadata retrieval with appropriate CI vs non-CI behavior. This prevents the entire optimization from failing due to a single corrupted or problematic file.


104-104: LGTM - Disk-based temporary file approach.

Writing to a .optimized temporary file is a good approach that enables size comparison before modifying the original.


112-115: LGTM - Conditional optimization with 20% threshold.

Good logic to skip optimization when the size reduction is not positive or below 20%. This ensures only meaningful optimizations are applied and properly cleans up the temporary file when not needed.

@Meldiron Meldiron marked this pull request as ready for review November 6, 2025 16:04
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6eddb8e and dd1bcd4.

⛔ Files ignored due to path filters (8)
  • static/email/discord.png is excluded by !**/*.png
  • static/email/x.png is excluded by !**/*.png
  • static/images/avatars/snezhanna.png is excluded by !**/*.png
  • static/images/avatars/steven.png is excluded by !**/*.png
  • static/images/blog/appwrite-pricing-update/add-ons.png is excluded by !**/*.png
  • static/images/blog/appwrite-pricing-update/bandwidth-project.png is excluded by !**/*.png
  • static/images/blog/appwrite-pricing-update/one-subscription.png is excluded by !**/*.png
  • static/images/blog/appwrite-pricing-update/project.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • .github/workflows/tests.yml (1 hunks)
  • .gitignore (1 hunks)
  • scripts/optimize-assets.js (1 hunks)

@TorstenDittmann TorstenDittmann merged commit ad41c79 into main Nov 7, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants