Skip to content

feat: single function to generate mod with output format option#82

Merged
SlayerOrnstein merged 8 commits intoWFCD:mainfrom
SlayerOrnstein:feat-formats
Nov 19, 2024
Merged

feat: single function to generate mod with output format option#82
SlayerOrnstein merged 8 commits intoWFCD:mainfrom
SlayerOrnstein:feat-formats

Conversation

@SlayerOrnstein
Copy link
Member

@SlayerOrnstein SlayerOrnstein commented Oct 19, 2024

What did you fix?

Use a single function to generate a mod image for riven and all other mods with optional output format option.

Also removed all the fonts and just decided to use one, Roboto is pretty good


Reproduction steps


Evidence/screenshot/link to line

Considerations

  • Does this contain a new dependency? No
  • Does this introduce opinionated data formatting or manual data entry? No
  • Does this pr include updated data files in a separate commit that can be reverted for a clean code-only pr? No
  • Have I run the linter? Yes
  • Is is a bug fix, feature request, or enhancement? Feature

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new modular image generation function that allows specifying output formats for generated images.
    • Added support for asynchronous file operations, enhancing performance and responsiveness.
    • New canvas output handling features allow encoding in multiple formats, including PNG, JPEG, and AVIF.
  • Bug Fixes

    • Updated font rendering to use "Roboto" for improved visual consistency.
  • Tests

    • Enhanced test suite to utilize asynchronous file operations for generating and verifying mod images.
  • Documentation

    • Updated TypeScript configuration to improve module resolution handling.

@SlayerOrnstein SlayerOrnstein changed the title perf: code refactor to better return cached images feat: single function to generate mod with output format option Oct 19, 2024
@coderabbitai
Copy link

coderabbitai bot commented Nov 15, 2024

Caution

Review failed

The pull request is closed.

Walkthrough

The changes in this pull request include updates to font specifications in rendering functions, enhancements to image generation functions by introducing an output parameter, and a transition to asynchronous file operations in utility functions. Additionally, new types and interfaces for canvas output have been added, and the test suite has been modified to accommodate these asynchronous changes. Finally, the TypeScript configuration has been updated to change module resolution settings.

Changes

File Change Summary
src/drawers.ts Updated font from "OpenSans" to "Roboto" in drawText and from "DroidSans" to "Roboto" in drawBackground. Adjusted logic for rankSlotStart in drawLegendaryFrame and drawFrame.
src/generator.ts Added output parameter to generateBasicMod and generateRivenMod, introduced generateMod function to determine mod type.
src/utils.ts Transitioned to asynchronous file operations, added exportCanvas, registered new fonts, and introduced types for canvas output.
tests/generator.spec.ts Updated tests to use asynchronous file operations and consolidated mod generation logic.
tsconfig.json Changed module and moduleResolution options from "Node16" to "NodeNext".

Possibly related PRs

Suggested reviewers

  • TobiTenno

Poem

In fields of code where rabbits play,
New fonts and functions hop today.
With async leaps, we dance and sing,
Generating mods, oh what joy they bring!
So let us celebrate, with a joyful cheer,
For every change brings us near! 🐇✨

Warning

Rate limit exceeded

@SlayerOrnstein has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 15 minutes and 57 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 049872b and 4976b5e.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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: 5

🧹 Outside diff range and nitpick comments (8)
tsconfig.json (1)

Line range hint 1-13: Consider adding type checking to the build process.

Since this is a strict TypeScript project, it would be beneficial to add type checking during the build process to catch any module resolution issues early.

Add these scripts to your package.json:

{
  "scripts": {
    "type-check": "tsc --noEmit",
    "build": "npm run type-check && your-existing-build-command"
  }
}
tests/generator.spec.ts (3)

2-4: Consider using async filesystem operations consistently

The code mixes synchronous (existsSync, mkdirSync) and asynchronous (readdir, writeFile) filesystem operations. For better consistency and performance, consider using async operations throughout.

-import { existsSync, mkdirSync } from 'node:fs';
+import { mkdir } from 'node:fs/promises';

Line range hint 14-27: Critical: Test function must be async to handle promises

The test function contains async operations but is not marked as async, which will cause the test runner to complete before the async operations finish. This could lead to false positives and race conditions.

-  test('run test', () => {
+  test('run test', async () => {

Line range hint 14-49: Consider restructuring the test suite for better organization and reliability

The current test structure could be improved in several ways:

  1. Split into smaller, focused test cases for each format
  2. Add cleanup using beforeEach/afterEach hooks
  3. Use test fixtures for mod data
  4. Add proper error messages for assertions

Example structure:

describe('Generate a mod', () => {
  const testPath = join('.', 'assets', 'tests');
  
  beforeEach(async () => {
    await mkdir(testPath, { recursive: true });
  });
  
  afterEach(async () => {
    // Clean up generated files
    await rm(testPath, { recursive: true, force: true });
  });
  
  for (const format of ['webp', 'jpeg', 'avif', 'png'] as const) {
    describe(`with ${format} format`, () => {
      it('should generate mod images correctly', async () => {
        // Test implementation for single format
      });
      
      it('should handle invalid mods gracefully', async () => {
        // Error handling test
      });
    });
  }
});
src/drawers.ts (1)

159-159: Consider extracting font specifications to constants.

While the font change to Roboto is good, consider extracting the repeated font specifications into constants to improve maintainability.

Example refactor:

+ const FONT_SPECS = {
+   PRIMARY: '300 16px "Roboto"',
+   SECONDARY: '12px "Roboto"'
+ };

  // In drawText function
-  context.font = '300 16px "Roboto"';
+  context.font = FONT_SPECS.PRIMARY;

  // In drawBackground function
-  context.font = '300 16px "Roboto"';
+  context.font = FONT_SPECS.PRIMARY;
src/utils.ts (3)

42-53: Use asynchronous fs.access instead of synchronous existsSync

To maintain consistency with the asynchronous operations in your codebase and avoid blocking the event loop, consider replacing the synchronous existsSync with the asynchronous fs.access from 'fs/promises'.

Apply this diff to update the code:

-import { existsSync } from 'fs';
+import { access } from 'fs/promises';
+import { constants } from 'fs';

...

// In fetchModPiece
- if (existsSync(filePath)) {
+ try {
+   await access(filePath, constants.F_OK);
    const image = await readFile(filePath);
    return loadImage(image);
+ } catch {
+   // File does not exist, proceed to download
+ }

...

- if (!existsSync(assetPath)) {
+ try {
+   await access(assetPath, constants.F_OK);
+ } catch {
    await mkdir(assetPath, { recursive: true });
+ }

await writeFile(filePath, image);

...

// In fetchPolarity
- if (existsSync(filePath)) {
+ try {
+   await access(filePath, constants.F_OK);
    const image = await readFile(filePath);
    return loadImage(image);
+ } catch {
+   // File does not exist, proceed to download
+ }

...

- if (!existsSync(assetPath)) {
+ try {
+   await access(assetPath, constants.F_OK);
+ } catch {
    await mkdir(assetPath, { recursive: true });
+ }

await writeFile(filePath, image);

This refactoring ensures non-blocking I/O operations and keeps the codebase consistently asynchronous.

Also applies to: 105-119


42-53: Refactor duplicated code in fetchModPiece and fetchPolarity

The functions fetchModPiece and fetchPolarity share similar logic for fetching and caching images. Consider extracting the common code into a shared utility function to reduce duplication and improve maintainability.

Also applies to: 105-119


189-200: Add a default case to the switch statement in exportCanvas

While all possible formats are covered by the Format type, adding a default case to the switch statement enhances robustness by handling any unexpected values, especially if the Format type is extended in the future.

Apply this diff:

export const exportCanvas = async (canvas: Canvas, output: CanvasOutput = { format: 'png' }) => {
  switch (output.format) {
    case 'png':
      return canvas.encode('png');
    case 'webp':
      return canvas.encode('webp', output.quality);
    case 'jpeg':
      return canvas.encode('jpeg', output.quality);
    case 'avif':
      return canvas.encode('avif', output.cfg);
+   default:
+     throw new Error(`Unsupported format: ${output.format}`);
  }
};

This ensures that any unsupported formats are caught with a clear error message.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between d89a80a and 9c25c66.

⛔ Files ignored due to path filters (6)
  • assets/fonts/DroidSans-Bold.ttf is excluded by !**/*.ttf
  • assets/fonts/DroidSans.ttf is excluded by !**/*.ttf
  • assets/fonts/OpenSans-Bold.ttf is excluded by !**/*.ttf
  • assets/fonts/OpenSans-Regular.ttf is excluded by !**/*.ttf
  • assets/fonts/Roboto-Bold.ttf is excluded by !**/*.ttf
  • assets/fonts/Roboto-Regular.ttf is excluded by !**/*.ttf
📒 Files selected for processing (5)
  • src/drawers.ts (3 hunks)
  • src/generator.ts (4 hunks)
  • src/utils.ts (5 hunks)
  • tests/generator.spec.ts (2 hunks)
  • tsconfig.json (1 hunks)
🔇 Additional comments (10)
tsconfig.json (1)

7-8: LGTM! Verify package.json configuration.

The upgrade to "NodeNext" for both module and moduleResolution is appropriate, especially given the transition to async operations. This provides better ESM support and improved package exports handling.

Let's verify the package.json configuration:

✅ Verification successful

Module configuration is correctly aligned

The codebase is properly configured for ESM:

  • Package.json has "type": "module"
  • All source files consistently use ESM syntax (import/export)
  • No CommonJS patterns (require/module.exports) were found
  • The TypeScript configuration with "NodeNext" matches the ESM setup
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify package.json configuration for ESM compatibility
# Expected: Should find "type": "module" in package.json

# Check package.json for module type
echo "Checking package.json configuration..."
jq '.type' package.json

# Look for potential ESM syntax in source files
echo "Checking for ESM syntax usage..."
rg -l "import\s+.*\s+from\s+" "src/"

Length of output: 276


Script:

#!/bin/bash
# Let's verify the actual import/export syntax in these files
# and check for any potential CommonJS patterns

# Check for ESM syntax in the identified files
echo "=== ESM Patterns ==="
for file in src/utils.ts src/generator.ts src/drawers.ts; do
    echo "=== $file ==="
    rg "^(import|export)" "$file" -A 1
done

# Check for potential CommonJS patterns
echo -e "\n=== CommonJS Patterns ==="
rg "require\(|module\.exports|exports\." "src/"

Length of output: 3529

src/generator.ts (4)

5-5: LGTM: Import statement is well-structured

The new imports are properly organized and include all necessary types and utilities for the enhanced functionality.


12-16: LGTM: Well-structured parameter updates and interface usage

The function signature changes are well-implemented:

  • Optional rank parameter with proper fallback
  • New output parameter with sensible default
  • Clean interface usage for frame parameters

Also applies to: 25-31


48-48: LGTM: Consistent implementation of output format support

The changes to generateRivenMod are minimal and maintain consistency with generateBasicMod:

  • Same output parameter default
  • Consistent use of exportCanvas

Also applies to: 85-86


88-95: ⚠️ Potential issue

Improve type safety and consistency in generateMod

Several concerns with the current implementation:

  1. Riven detection using string.includes() is fragile
  2. Inconsistent rank parameter handling between mod types
  3. Unsafe type assertion to RivenMod

Consider this safer implementation:

 export const generateMod = async (
   mod: Mod,
   rank?: number,
   output: CanvasOutput = { format: 'png' }
 ): Promise<Buffer> => {
-  const isRiven = mod.name?.includes('Riven');
+  // Type guard function
+  const isRivenMod = (mod: Mod): mod is RivenMod => {
+    return 'attributes' in mod && 'veiled' in mod;
+  };
 
-  return isRiven ? generateRivenMod(mod as RivenMod, output) : generateBasicMod(mod, rank ?? mod.fusionLimit, output);
+  if (isRivenMod(mod)) {
+    return generateRivenMod(mod, output);
+  }
+  return generateBasicMod(mod, rank, output);
 };

This implementation:

  • Uses a proper type guard instead of string matching
  • Maintains consistent rank parameter handling
  • Eliminates unsafe type assertion

Let's verify the Riven mod structure:

src/drawers.ts (4)

103-103: LGTM: Font standardization for mod name.

The font change to Roboto aligns with the PR objective to standardize fonts.


108-108: LGTM: Font standardization for mod description.

The font change maintains readability while aligning with the standardization effort.


122-122: LGTM: Font standardization for compatibility name.

The font change is consistent with other text elements.


Line range hint 103-122: Verify Roboto font registration.

Ensure that the Roboto font files are properly registered in the registerFonts function.

✅ Verification successful

Roboto font registration is properly implemented

The verification confirms that:

  • The registerFonts function correctly registers both Roboto Regular and Bold variants
  • The font files exist at the expected paths:
    • assets/fonts/Roboto-Regular.ttf
    • assets/fonts/Roboto-Bold.ttf
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if Roboto font files are properly registered

# Check for Roboto font registration in utils.ts
rg -A 5 "registerFonts" src/utils.ts

# Check for Roboto font files in the project
fd -t f "Roboto.*\.(ttf|otf)"

Length of output: 376

src/utils.ts (1)

166-167: Fonts updated to 'Roboto' successfully

The fonts have been updated to 'Roboto', registering both 'Roboto-Bold.ttf' and 'Roboto-Regular.ttf' under the 'Roboto' family. This change standardizes font usage and ensures consistency in text rendering across the application.

Copy link
Member

@TobiTenno TobiTenno left a comment

Choose a reason for hiding this comment

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

see above

Copy link

@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

🧹 Outside diff range and nitpick comments (3)
tests/generator.spec.ts (2)

2-4: Consider using async fs operations consistently

The file mixes synchronous (existsSync, mkdirSync) and asynchronous (readdir, writeFile) fs operations. For better consistency and performance, consider using async operations throughout.

-import { existsSync, mkdirSync } from 'node:fs';
+import { mkdir } from 'node:fs/promises';

Line range hint 14-49: Consider restructuring the test suite for better organization

The current test structure combines both generation and verification in a single test. Consider:

  1. Separating the tests into smaller, focused test cases for each format
  2. Using describe.each for format-specific tests
  3. Creating helper functions for common operations

Example structure:

describe('Mod Generation', () => {
  const formats = ['webp', 'jpeg', 'avif', 'png'] as const;
  
  describe.each(formats)('Format: %s', (format) => {
    test('generates all mod types correctly', async () => {
      // Test specific format
    });
    
    test('verifies generated files', async () => {
      // Verify specific format
    });
  });
});
src/utils.ts (1)

162-163: Add error handling for font registration.

While the font standardization to Roboto is good, consider adding error handling to gracefully handle missing font files and verify successful registration.

 export const registerFonts = () => {
   const fontPath = join('.', 'assets', 'fonts');
-  GlobalFonts.registerFromPath(join(fontPath, 'Roboto-Bold.ttf'), 'Roboto');
-  GlobalFonts.registerFromPath(join(fontPath, 'Roboto-Regular.ttf'), 'Roboto');
+  try {
+    const boldPath = join(fontPath, 'Roboto-Bold.ttf');
+    const regularPath = join(fontPath, 'Roboto-Regular.ttf');
+    
+    if (!existsSync(boldPath) || !existsSync(regularPath)) {
+      throw new Error('Required Roboto font files are missing');
+    }
+    
+    GlobalFonts.registerFromPath(boldPath, 'Roboto');
+    GlobalFonts.registerFromPath(regularPath, 'Roboto');
+  } catch (error) {
+    console.error('Failed to register Roboto fonts:', error);
+    throw error; // Re-throw to let caller handle the error
+  }
 };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9c25c66 and 751fce1.

📒 Files selected for processing (3)
  • src/generator.ts (4 hunks)
  • src/utils.ts (5 hunks)
  • tests/generator.spec.ts (2 hunks)
🧰 Additional context used
🪛 GitHub Check: Verify
src/utils.ts

[warning] 198-198:
Unexpected console statement

🔇 Additional comments (10)
tests/generator.spec.ts (2)

29-41: Previous review comment about async handling issues is still valid

The current implementation has the same critical issues as identified in the previous review.


44-49: Previous review comment about verification loop issues is still valid

The verification loop has the same async handling and timing issues as identified in the previous review.

src/generator.ts (4)

5-5: LGTM: Import changes align with new functionality

The new imports support the output format options feature.


12-16: LGTM: Function signature improvements

The updated signature properly handles:

  • Optional rank parameter
  • New output format option
  • Nullable return type

42-44: Redundant PNG encoding still present

This issue was previously flagged but hasn't been addressed yet.


47-50: LGTM: Consistent output format handling

The changes align with the new output format feature and maintain consistency with generateBasicMod.

Also applies to: 87-88

src/utils.ts (4)

42-52: LGTM! Proper async file operations implementation.

The file operations are now correctly implemented with proper separation of concerns and error handling. Previous review comments' concerns about file writing logic have been addressed.


103-116: LGTM! Consistent async implementation.

The async file operations are implemented consistently with fetchModPiece, following the same robust pattern.


177-183: LGTM! Well-defined type definitions.

The type definitions for canvas output formats are clear, comprehensive, and properly typed with optional parameters.


162-163: Verify font usage across the codebase.

Let's ensure all components are updated to use the new Roboto font.

✅ Verification successful

Font usage is consistent across the codebase

All font references in the codebase are already using "Roboto" font family. No references to legacy fonts like DroidSans or OpenSans were found. The font registration in utils.ts aligns with the font usage in drawers.ts.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining references to old fonts
rg -i "DroidSans|OpenSans" --type ts

# Search for font family specifications in drawing code
rg "font\s*=|fontFamily" --type ts

Length of output: 285

Copy link

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (1)
src/utils.ts (1)

177-183: Add JSDoc documentation for new types

The new types would benefit from documentation explaining their purpose and constraints.

+/**
+ * Supported output formats for canvas encoding
+ */
 export type Format = 'webp' | 'jpeg' | 'avif' | 'png';

+/**
+ * Configuration options for canvas export
+ * @property quality - Quality setting (0-100) for webp and jpeg formats
+ * @property format - Output format type
+ * @property cfg - Advanced configuration for avif format
+ */
 export interface CanvasOutput {
   quality?: number;
   format: Format;
   cfg?: AvifConfig;
 }
🛑 Comments failed to post (2)
src/utils.ts (2)

162-163: 🛠️ Refactor suggestion

Add error handling for font registration

While the font standardization to Roboto is good, the function should handle potential font loading failures.

 export const registerFonts = () => {
   const fontPath = join('.', 'assets', 'fonts');
-  GlobalFonts.registerFromPath(join(fontPath, 'Roboto-Bold.ttf'), 'Roboto');
-  GlobalFonts.registerFromPath(join(fontPath, 'Roboto-Regular.ttf'), 'Roboto');
+  try {
+    const boldPath = join(fontPath, 'Roboto-Bold.ttf');
+    const regularPath = join(fontPath, 'Roboto-Regular.ttf');
+    
+    if (!existsSync(boldPath) || !existsSync(regularPath)) {
+      throw new Error('Required Roboto font files are missing');
+    }
+    
+    GlobalFonts.registerFromPath(boldPath, 'Roboto');
+    GlobalFonts.registerFromPath(regularPath, 'Roboto');
+  } catch (error) {
+    throw new Error(`Failed to register fonts: ${error.message}`);
+  }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export const registerFonts = () => {
  const fontPath = join('.', 'assets', 'fonts');
  try {
    const boldPath = join(fontPath, 'Roboto-Bold.ttf');
    const regularPath = join(fontPath, 'Roboto-Regular.ttf');
    
    if (!existsSync(boldPath) || !existsSync(regularPath)) {
      throw new Error('Required Roboto font files are missing');
    }
    
    GlobalFonts.registerFromPath(boldPath, 'Roboto');
    GlobalFonts.registerFromPath(regularPath, 'Roboto');
  } catch (error) {
    throw new Error(`Failed to register fonts: ${error.message}`);
  }
};

185-204: ⚠️ Potential issue

Fix error handling and quality validation in exportCanvas

Several issues need to be addressed:

  1. Error handling currently swallows errors and returns undefined
  2. Quality validation is inconsistent with common image quality scales
  3. Console.error usage should be replaced with proper error handling
 export const exportCanvas = async (canvas: Canvas, output: CanvasOutput = { format: 'png' }) => {
   const quality = output.quality || output.cfg?.quality;
-  if (quality !== undefined && (quality < 0 || quality > 100)) {
-    throw new Error('quality cannot be less then 0 or more then 100');
+  if (quality !== undefined) {
+    if (output.format === 'avif') {
+      if (quality < 0 || quality > 100) {
+        throw new Error('AVIF quality must be between 0 and 100');
+      }
+    } else {
+      // WebP and JPEG expect quality between 0 and 1
+      if (quality < 0 || quality > 1) {
+        throw new Error('Quality must be between 0 and 1 for WebP and JPEG formats');
+      }
+    }
   }

   try {
     switch (output.format) {
       case 'png':
         return await canvas.encode('png');
       case 'webp':
         return await canvas.encode('webp', output.quality);
       case 'jpeg':
         return await canvas.encode('jpeg', output.quality);
       case 'avif':
         return await canvas.encode('avif', output.cfg);
+      default:
+        throw new Error(`Unsupported format: ${output.format}`);
     }
-  } catch {
-    console.error(`failed to export canvas as ${output.format}`);
+  } catch (error) {
+    throw new Error(`Failed to export canvas as ${output.format}: ${error.message}`);
   }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export const exportCanvas = async (canvas: Canvas, output: CanvasOutput = { format: 'png' }) => {
  const quality = output.quality || output.cfg?.quality;
  if (quality !== undefined) {
    if (output.format === 'avif') {
      if (quality < 0 || quality > 100) {
        throw new Error('AVIF quality must be between 0 and 100');
      }
    } else {
      // WebP and JPEG expect quality between 0 and 1
      if (quality < 0 || quality > 1) {
        throw new Error('Quality must be between 0 and 1 for WebP and JPEG formats');
      }
    }
  }

  try {
    switch (output.format) {
      case 'png':
        return await canvas.encode('png');
      case 'webp':
        return await canvas.encode('webp', output.quality);
      case 'jpeg':
        return await canvas.encode('jpeg', output.quality);
      case 'avif':
        return await canvas.encode('avif', output.cfg);
      default:
        throw new Error(`Unsupported format: ${output.format}`);
    }
  } catch (error) {
    throw new Error(`Failed to export canvas as ${output.format}: ${error.message}`);
  }
};
🧰 Tools
🪛 GitHub Check: Verify

[warning] 203-203:
Unexpected console statement

Copy link

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (2)
tests/generator.spec.ts (2)

2-4: Replace sync operations with async alternatives

For consistency with the async operations already in use, consider replacing the sync fs operations with their async counterparts.

-import { existsSync, mkdirSync } from 'node:fs';
+import { mkdir } from 'node:fs/promises';

15-23: Enhance test coverage with additional test cases

Consider adding:

  1. Negative test cases (invalid mod paths, unsupported formats)
  2. Verification of generated image properties (dimensions, format)
  3. Edge cases (mods with special characters in names)

Example test cases to add:

test('should handle invalid mod paths', async () => {
  const invalidMod = find.findItem('invalid_path') as Mod;
  const result = await generateMod(invalidMod, undefined, { format: 'png' as Format });
  assert.equal(result, undefined);
});

test('should handle unsupported format', async () => {
  const mod = find.findItem(mods[0]) as Mod;
  await assert.rejects(
    async () => generateMod(mod, undefined, { format: 'invalid' as Format }),
    /Unsupported format/
  );
});
🛑 Comments failed to post (1)
src/utils.ts (1)

185-205: ⚠️ Potential issue

Several improvements needed in exportCanvas function

  1. Quality validation range seems incorrect:
    • The error message suggests a 0-100 range
    • Most image encoding libraries expect quality in 0-1 range
  2. Error handling needs improvement:
    • Function silently returns undefined on error
    • Console.error should be replaced with proper error handling
  3. Type safety can be enhanced:
    • Switch statement should handle all possible cases

Apply this diff to fix these issues:

 export const exportCanvas = async (canvas: Canvas, output: CanvasOutput = { format: 'png' }) => {
   const quality = output.quality || output.cfg?.quality;
-  if (quality !== undefined && (quality < 0 || quality > 100)) {
-    throw new Error('quality cannot be less then 0 or more then 100');
+  if (quality !== undefined && (quality < 0 || quality > 1)) {
+    throw new Error('Quality must be between 0 and 1');
   }

   try {
     switch (output.format) {
       case 'png':
         return await canvas.encode('png');
       case 'webp':
         return await canvas.encode('webp', output.quality);
       case 'jpeg':
         return await canvas.encode('jpeg', output.quality);
       case 'avif':
         return await canvas.encode('avif', output.cfg);
+      default:
+        throw new Error(`Unsupported format: ${output.format}`);
     }
-  } catch {
-    console.error(`failed to export canvas as ${output.format}`);
+  } catch (error) {
+    throw new Error(`Failed to export canvas as ${output.format}: ${error.message}`);
   }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export const exportCanvas = async (canvas: Canvas, output: CanvasOutput = { format: 'png' }) => {
  const quality = output.quality || output.cfg?.quality;
  if (quality !== undefined && (quality < 0 || quality > 1)) {
    throw new Error('Quality must be between 0 and 1');
  }

  try {
    switch (output.format) {
      case 'png':
        return await canvas.encode('png');
      case 'webp':
        return await canvas.encode('webp', output.quality);
      case 'jpeg':
        return await canvas.encode('jpeg', output.quality);
      case 'avif':
        return await canvas.encode('avif', output.cfg);
      default:
        throw new Error(`Unsupported format: ${output.format}`);
    }
  } catch (error) {
    throw new Error(`Failed to export canvas as ${output.format}: ${error.message}`);
  }
};
🧰 Tools
🪛 GitHub Check: Verify

[warning] 203-203:
Unexpected console statement

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