Skip to content

fix: reduce duplicate image downloads#908

Open
SlayerOrnstein wants to merge 8 commits intomasterfrom
simple-image-name
Open

fix: reduce duplicate image downloads#908
SlayerOrnstein wants to merge 8 commits intomasterfrom
simple-image-name

Conversation

@SlayerOrnstein
Copy link
Copy Markdown
Member

@SlayerOrnstein SlayerOrnstein commented Apr 12, 2026

What did you fix?

Reduce duplication by leaning more on the image manifest and hashes.

The idea is to just use the internal name of the image resource as the image name to reduce complexity in addImageName. This also allows saveImage to be smarter about reusing image names when a duplicate hash is found in the textureLocation (hashes being the same means the image is the same, even if the filenames are different). This also allows saveImage to properly handle situations where the filename is the same but the resource is different as is the case with Ember and her monotone glyph also being called Ember.png in these cases the Item name is used instead.

Any updates or last minute changes by saveImage will also update the corresponding Item and push writing the json till after the images are downloaded.


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? Yes
  • Does this pr include updated data files in a separate commit that can be reverted for a clean code-only pr? Yes
  • Have I run the linter? Yes
  • Is it a bugfix, feature request, or enhancement? Enhancement

Summary by CodeRabbit

  • Bug Fixes

    • Improved image deduplication system to prevent unnecessary reprocessing
    • Added filesystem collision handling for generated assets
  • Refactor

    • Optimized build workflow with streamlined asset processing
    • Simplified asset naming logic for better maintainability

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

Warning

Rate limit exceeded

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

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 57 minutes and 42 seconds.

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c16a075f-6a20-4483-9dcc-750730b6a8a6

📥 Commits

Reviewing files that changed from the base of the PR and between c7704ee and d2a1730.

⛔ Files ignored due to path filters (2)
  • data/img/arcane.png is excluded by !**/*.png
  • data/img/blueprint.png is excluded by !**/*.png
📒 Files selected for processing (2)
  • build/build.ts
  • test/index.spec.mjs
📝 Walkthrough

Walkthrough

The PR refactors the image processing pipeline in the build system. It simplifies image naming by removing hash-based collision resolution in the parser and replaces filename deduplication tracking with a hash-keyed processed file map in the build process. The JSON generation control flow is restructured to separate merging category data from writing output files, and the saveJson API signature is updated to accept a bundle parameter.

Changes

Cohort / File(s) Summary
Image Processing & Naming
build/build.ts, build/parser.ts
Simplified image naming and deduplication: removed SHA256 hashing and special-case naming rules from parser; replaced string array duplicate tracking with hash-keyed processed map in build; added filesystem collision handling and tightened download condition to hash-based comparison; refactored saveJson API to accept bundle parameter and return Promise<void> instead of Promise<Item[]>.
Test Updates
test/utilities/find.spec.mjs
Updated expected image name for riven mod test case from hash-based filename to simplified manifest-derived name.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested labels

Scope: Scraper, Scope: Data

Suggested reviewers

  • AyAyEm
  • TobiTenno

Poem

🐰 A builder's refactor, oh what a sight!
Hash maps and merges dance into the light,
Simplifying names with manifest grace,
Nightwave vanishes—collision's no more race! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly addresses the primary objective of the changeset, which is to reduce duplicate image downloads through improved hash-based deduplication logic.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch simple-image-name

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.

@SlayerOrnstein SlayerOrnstein force-pushed the simple-image-name branch 2 times, most recently from 845d0ee to 1db7259 Compare April 14, 2026 11:09
@SlayerOrnstein SlayerOrnstein marked this pull request as ready for review April 14, 2026 21:53
@SlayerOrnstein SlayerOrnstein requested a review from a team as a code owner April 14, 2026 21:53
@SlayerOrnstein SlayerOrnstein requested a review from AyAyEm April 14, 2026 21:53
Copy link
Copy Markdown

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
build/build.ts (1)

184-188: ⚠️ Potential issue | 🔴 Critical

Keep the image-name reconciliation pass even when the manifest hash is unchanged.

saveImage() now mutates item.imageName for duplicate hashes and filename conflicts, but Line 188 returns before any of that runs when only non-manifest data changed. saveJson() then re-emits the parser's raw names, so duplicate items can point at files that were never written. Let the loop run and rely on the per-item cached?.hash !== hash check to skip actual downloads.

Proposed fix
   async saveImages(items: Item[], manifest: ImageManifest): Promise<void> {
-    // No need to go through every item if the manifest didn't change. I'm
-    // guessing the `fileTime` key in each element works more or less like a
-    // hash, so any change to that changes the hash of the full thing.
-    if (!hashManager.hasChanged('Manifest')) return;
     const bar = new Progress('Fetching Images', items.length);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build/build.ts` around lines 184 - 188, The early return in saveImages
prevents the image-name reconciliation that saveImage performs, so remove or
alter the "if (!hashManager.hasChanged('Manifest')) return;" logic in saveImages
to always iterate items; instead, only skip the actual download per-item by
relying on the existing per-item check (cached?.hash !== hash) inside saveImage.
Ensure saveImages still calls saveImage for every Item to allow mutation of
item.imageName (resolving duplicate hashes/filename conflicts) while letting
saveImage decide whether to download based on cached?.hash.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@build/build.ts`:
- Around line 257-268: The collision check currently treats any existing file as
a collision (using existsSync(filePath)), which can rename the current item's
own previous output and break later hash checks; modify the condition so we only
treat it as a collision when the on-disk file is owned by a different item—e.g.
check ownership via cached?.uniqueName or the processed[hash] mapping instead of
existsSync alone: only enter the rename branch when existsSync(filePath) &&
cached?.uniqueName !== item.uniqueName (or processed[hash] !== item.imageName),
leaving the file untouched if the existing file belongs to the same uniqueName,
and ensure the subsequent cached/hash logic still prevents unnecessary writes
when the hash matches.

In `@build/parser.ts`:
- Around line 575-588: In addImageName(), normalize backslashes on
image.textureLocation (same as saveImage() uses) before splitting: const
imageStub = image.textureLocation.replace(/\\/g, '/'); then derive uniqueName =
imageStub.split('!')[0] and treat an empty string as invalid (if (!uniqueName) {
warnings.missingImage.push(item.name); return; }). Next get imageName =
uniqueName.split('/').reverse()[0] and reject empty imageName similarly. After
calling sanitize(imageName), validate the sanitized result is non-empty before
assigning item.imageName = sanitized; otherwise push to warnings.missingImage
and return. Reference: addImageName(), image.textureLocation, saveImage(),
sanitize(), warnings.missingImage, item.imageName.

---

Outside diff comments:
In `@build/build.ts`:
- Around line 184-188: The early return in saveImages prevents the image-name
reconciliation that saveImage performs, so remove or alter the "if
(!hashManager.hasChanged('Manifest')) return;" logic in saveImages to always
iterate items; instead, only skip the actual download per-item by relying on the
existing per-item check (cached?.hash !== hash) inside saveImage. Ensure
saveImages still calls saveImage for every Item to allow mutation of
item.imageName (resolving duplicate hashes/filename conflicts) while letting
saveImage decide whether to download based on cached?.hash.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8d4d278-bf7b-4e7c-9653-5f4058e22dd8

📥 Commits

Reviewing files that changed from the base of the PR and between c47da0b and 720d881.

⛔ Files ignored due to path filters (296)
  • data/img/10YearAnniversaryLoginSongItemStoreIcon.png is excluded by !**/*.png
  • data/img/13angTV.png is excluded by !**/*.png
  • data/img/1999Aoi.png is excluded by !**/*.png
  • data/img/1999BoybandPoster.png is excluded by !**/*.png
  • data/img/1999ComicCoverPoster.png is excluded by !**/*.png
  • data/img/1999CommunityARGEmblem.png is excluded by !**/*.png
  • data/img/1999Drippy.png is excluded by !**/*.png
  • data/img/1999EntHybridPistol.png is excluded by !**/*.png
  • data/img/1999FlareBandPoster.png is excluded by !**/*.png
  • data/img/1999InfShotgunWeapon.png is excluded by !**/*.png
  • data/img/1999InfSporePistolWeapon.png is excluded by !**/*.png
  • data/img/1999OnlyneWallPoster.png is excluded by !**/*.png
  • data/img/1999Pizza.png is excluded by !**/*.png
  • data/img/1999PrologueQuestKeyChain.png is excluded by !**/*.png
  • data/img/1999QuestKeyChain.png is excluded by !**/*.png
  • data/img/1999ResourceCommonA.png is excluded by !**/*.png
  • data/img/1999ResourceCommonAContainer.png is excluded by !**/*.png
  • data/img/1999ResourceCommonB.png is excluded by !**/*.png
  • data/img/1999ResourceCommonBContainer.png is excluded by !**/*.png
  • data/img/1999ResourceDefense.png is excluded by !**/*.png
  • data/img/1999ResourceRareA.png is excluded by !**/*.png
  • data/img/1999ResourceRareAContainer.png is excluded by !**/*.png
  • data/img/1999ResourceUncommonA.png is excluded by !**/*.png
  • data/img/1999ResourceUncommonAContainer.png is excluded by !**/*.png
  • data/img/1999ResourceUncommonB.png is excluded by !**/*.png
  • data/img/1999ResourceUncommonBContainer.png is excluded by !**/*.png
  • data/img/1999TankHoodOrnament.png is excluded by !**/*.png
  • data/img/1999TrophyBronze.png is excluded by !**/*.png
  • data/img/1999Y2KEarB.png is excluded by !**/*.png
  • data/img/1999Y2KEarC.png is excluded by !**/*.png
  • data/img/1999Y2KEyeA.png is excluded by !**/*.png
  • data/img/1999Y2KEyeB.png is excluded by !**/*.png
  • data/img/1999Y2KEyeC.png is excluded by !**/*.png
  • data/img/2020ZerOGlyph.png is excluded by !**/*.png
  • data/img/2025GCX.png is excluded by !**/*.png
  • data/img/4MORI4N.png is excluded by !**/*.png
  • data/img/7thAnniversaryPoster.png is excluded by !**/*.png
  • data/img/8thAnniversaryPoster.png is excluded by !**/*.png
  • data/img/A.png is excluded by !**/*.png
  • data/img/AGGP.png is excluded by !**/*.png
  • data/img/AHCommonPickup.png is excluded by !**/*.png
  • data/img/AHR.png is excluded by !**/*.png
  • data/img/AHRarePickup.png is excluded by !**/*.png
  • data/img/AHUncommonPickup.png is excluded by !**/*.png
  • data/img/AK47Weapon.png is excluded by !**/*.png
  • data/img/AVReceiver.png is excluded by !**/*.png
  • data/img/AbacusSmall.png is excluded by !**/*.png
  • data/img/AbberaPrime.png is excluded by !**/*.png
  • data/img/AbilityBlock.jpg is excluded by !**/*.jpg
  • data/img/AbilityDurationMod.jpg is excluded by !**/*.jpg
  • data/img/AbilityEfficiencyMod.jpg is excluded by !**/*.jpg
  • data/img/AbilityRangeMod.jpg is excluded by !**/*.jpg
  • data/img/AbilityStrengthMod.jpg is excluded by !**/*.jpg
  • data/img/Abrasys.png is excluded by !**/*.png
  • data/img/AbyssOfDagathLoginSongItemStoreIcon.png is excluded by !**/*.png
  • data/img/AcceltraDeluxeIISkin.png is excluded by !**/*.png
  • data/img/AcceltraDeluxeSkin.png is excluded by !**/*.png
  • data/img/AcceltraPrime.png is excluded by !**/*.png
  • data/img/AccessibleGamer.png is excluded by !**/*.png
  • data/img/AccuracyWhileAiming.jpg is excluded by !**/*.jpg
  • data/img/AckAndBrunt.png is excluded by !**/*.png
  • data/img/AckAndBruntConclave.png is excluded by !**/*.png
  • data/img/AckAndBruntDayOfTheDead.png is excluded by !**/*.png
  • data/img/AckAndBruntNigtwave.png is excluded by !**/*.png
  • data/img/AckBruntIncarnonAdapter.png is excluded by !**/*.png
  • data/img/AckBruntNightwatchMod.jpg is excluded by !**/*.jpg
  • data/img/AcolyteSynpai.png is excluded by !**/*.png
  • data/img/Acrid.jpg is excluded by !**/*.jpg
  • data/img/Acrid.png is excluded by !**/*.png
  • data/img/ActivateDeimosRequiemTotem.png is excluded by !**/*.png
  • data/img/Actuator.png is excluded by !**/*.png
  • data/img/AdelfosSelene.png is excluded by !**/*.png
  • data/img/AdikDarkCero.png is excluded by !**/*.png
  • data/img/AdmiralBahroo.png is excluded by !**/*.png
  • data/img/AdrenalStim.png is excluded by !**/*.png
  • data/img/AdultHeadARemastered.png is excluded by !**/*.png
  • data/img/AdultHeadBRemastered.png is excluded by !**/*.png
  • data/img/AdultHeadCRemastered.png is excluded by !**/*.png
  • data/img/AdultHeadDRemastered.png is excluded by !**/*.png
  • data/img/AdultHeadERemastered.png is excluded by !**/*.png
  • data/img/AdultHeadFRemastered.png is excluded by !**/*.png
  • data/img/AdultHeadFemaleA.png is excluded by !**/*.png
  • data/img/AdultHeadFemaleB.png is excluded by !**/*.png
  • data/img/AdultHeadFemaleC.png is excluded by !**/*.png
  • data/img/AdultHeadFemaleD.png is excluded by !**/*.png
  • data/img/AdultHeadFemaleE.png is excluded by !**/*.png
  • data/img/AdultHeadFemaleF.png is excluded by !**/*.png
  • data/img/AdultHeadFemaleG.png is excluded by !**/*.png
  • data/img/AdultHeadFemaleH.png is excluded by !**/*.png
  • data/img/AdultHeadFemaleI.png is excluded by !**/*.png
  • data/img/AdultHeadFemaleJ.png is excluded by !**/*.png
  • data/img/AdultHeadFemaleK.png is excluded by !**/*.png
  • data/img/AdultHeadFemaleL.png is excluded by !**/*.png
  • data/img/AdultHeadFemaleM.png is excluded by !**/*.png
  • data/img/AdultHeadGRemastered.png is excluded by !**/*.png
  • data/img/AdultHeadHRemastered.png is excluded by !**/*.png
  • data/img/AdultHeadIRemastered.png is excluded by !**/*.png
  • data/img/AdultHeadJRemastered.png is excluded by !**/*.png
  • data/img/AdultHeadKRemastered.png is excluded by !**/*.png
  • data/img/AdultHeadLRemastered.png is excluded by !**/*.png
  • data/img/AdultHeadMRemastered.png is excluded by !**/*.png
  • data/img/AdultHeadMaleA.png is excluded by !**/*.png
  • data/img/AdultHeadMaleB.png is excluded by !**/*.png
  • data/img/AdultHeadMaleC.png is excluded by !**/*.png
  • data/img/AdultHeadMaleD.png is excluded by !**/*.png
  • data/img/AdultHeadMaleE.png is excluded by !**/*.png
  • data/img/AdultHeadMaleF.png is excluded by !**/*.png
  • data/img/AdultHeadMaleG.png is excluded by !**/*.png
  • data/img/AdultHeadMaleH.png is excluded by !**/*.png
  • data/img/AdultHeadMaleI.png is excluded by !**/*.png
  • data/img/AdultHeadMaleJ.png is excluded by !**/*.png
  • data/img/AdultHeadMaleK.png is excluded by !**/*.png
  • data/img/AdultHeadMaleL.png is excluded by !**/*.png
  • data/img/AdultHeadMaleM.png is excluded by !**/*.png
  • data/img/AdultHeadNRemastered.png is excluded by !**/*.png
  • data/img/AdultHeadORemastered.png is excluded by !**/*.png
  • data/img/AdultHeadPRemastered.png is excluded by !**/*.png
  • data/img/AdultHeadQRemastered.png is excluded by !**/*.png
  • data/img/AdultHeadRRemastered.png is excluded by !**/*.png
  • data/img/AdultHeadSRemastered.png is excluded by !**/*.png
  • data/img/AdultHeadTRemastered.png is excluded by !**/*.png
  • data/img/AdultOperatorAgile.png is excluded by !**/*.png
  • data/img/AdultOperatorNoble.png is excluded by !**/*.png
  • data/img/AeonKnight.png is excluded by !**/*.png
  • data/img/AesopYOLIANGlyph.png is excluded by !**/*.png
  • data/img/Afuris.png is excluded by !**/*.png
  • data/img/AfurisForestCamoSkin.png is excluded by !**/*.png
  • data/img/AfurisPrime.png is excluded by !**/*.png
  • data/img/Agkuza.png is excluded by !**/*.png
  • data/img/AimGlide.png is excluded by !**/*.png
  • data/img/AirHockey.png is excluded by !**/*.png
  • data/img/AirSlideBoost.jpg is excluded by !**/*.jpg
  • data/img/AirSupportCarpetBomb.png is excluded by !**/*.png
  • data/img/AirSupportGrineer.png is excluded by !**/*.png
  • data/img/AirSupportLiset.png is excluded by !**/*.png
  • data/img/AirSupportMantis.png is excluded by !**/*.png
  • data/img/AirSupportNightwave.png is excluded by !**/*.png
  • data/img/AirSupportRareInstinct.png is excluded by !**/*.png
  • data/img/AirSupportSentryTurret.png is excluded by !**/*.png
  • data/img/Ajingom.png is excluded by !**/*.png
  • data/img/AkSura.png is excluded by !**/*.png
  • data/img/Akariayataka.png is excluded by !**/*.png
  • data/img/AkariusPrime.png is excluded by !**/*.png
  • data/img/Akbolto.png is excluded by !**/*.png
  • data/img/AkboltoOrmolu.png is excluded by !**/*.png
  • data/img/AkboltoPrime.png is excluded by !**/*.png
  • data/img/Akbronco.png is excluded by !**/*.png
  • data/img/AkbroncoPrime.png is excluded by !**/*.png
  • data/img/AkbroncoPrimeViralMod.jpg is excluded by !**/*.jpg
  • data/img/Akjagara.png is excluded by !**/*.png
  • data/img/AkjagaraIridosSkin.png is excluded by !**/*.png
  • data/img/AkjagaraPrime.png is excluded by !**/*.png
  • data/img/Aklato.png is excluded by !**/*.png
  • data/img/AklatoConclaveSkin.png is excluded by !**/*.png
  • data/img/AklatoDayOfTheDead.png is excluded by !**/*.png
  • data/img/AklatoKintsugi.png is excluded by !**/*.png
  • data/img/AklatoNocturne.png is excluded by !**/*.png
  • data/img/AklatoRixtyMOLSkin.png is excluded by !**/*.png
  • data/img/Aklex.png is excluded by !**/*.png
  • data/img/AklexConclaveSkin.png is excluded by !**/*.png
  • data/img/AklexPrime.png is excluded by !**/*.png
  • data/img/Akmagnus.png is excluded by !**/*.png
  • data/img/AkmagnusDakila.png is excluded by !**/*.png
  • data/img/AkmagnusHiveLight.png is excluded by !**/*.png
  • data/img/AkmagnusObsidian.png is excluded by !**/*.png
  • data/img/AkmagnusPrime.png is excluded by !**/*.png
  • data/img/Akrabu.png is excluded by !**/*.png
  • data/img/AkrabuPrime.png is excluded by !**/*.png
  • data/img/Aksomati.png is excluded by !**/*.png
  • data/img/AksomatiPrime.png is excluded by !**/*.png
  • data/img/Akstileto.png is excluded by !**/*.png
  • data/img/AkstiletoPrime.png is excluded by !**/*.png
  • data/img/AkstilettoConclaveSkin.png is excluded by !**/*.png
  • data/img/AkstilettoPrimeAmmoEfficiencyMod.jpg is excluded by !**/*.jpg
  • data/img/Akvasto.png is excluded by !**/*.png
  • data/img/AkvastoConclaveSkin.png is excluded by !**/*.png
  • data/img/AkvastoDayOfTheDeadSkin.png is excluded by !**/*.png
  • data/img/AkvastoForestCamoSkin.png is excluded by !**/*.png
  • data/img/AkvastoPrime.png is excluded by !**/*.png
  • data/img/AkvastoVoidSkin.png is excluded by !**/*.png
  • data/img/Akzani.png is excluded by !**/*.png
  • data/img/AladV.png is excluded by !**/*.png
  • data/img/AlainLove.png is excluded by !**/*.png
  • data/img/Albrecht2in1Display.png is excluded by !**/*.png
  • data/img/AlbrechtHatCommunityGlyph.png is excluded by !**/*.png
  • data/img/AlbrechtPortrait.png is excluded by !**/*.png
  • data/img/AlchemistAgile.png is excluded by !**/*.png
  • data/img/AlchemistDistill.png is excluded by !**/*.png
  • data/img/AlchemistNoble.png is excluded by !**/*.png
  • data/img/AlchemistRush.png is excluded by !**/*.png
  • data/img/AlchemistSerpent.png is excluded by !**/*.png
  • data/img/AlchemistTransmuteAugmentCard.jpg is excluded by !**/*.jpg
  • data/img/AlchemistTransmuter.png is excluded by !**/*.png
  • data/img/Alertium.png is excluded by !**/*.png
  • data/img/AlexanderDario.png is excluded by !**/*.png
  • data/img/AlexandraLive.png is excluded by !**/*.png
  • data/img/AlloyPlate.png is excluded by !**/*.png
  • data/img/AlternoxDeluxeSkin.png is excluded by !**/*.png
  • data/img/AlternoxPrime.png is excluded by !**/*.png
  • data/img/Althani.png is excluded by !**/*.png
  • data/img/Altra.png is excluded by !**/*.png
  • data/img/AltraPrime.png is excluded by !**/*.png
  • data/img/Alyekk.png is excluded by !**/*.png
  • data/img/AmalgamArgonak.jpg is excluded by !**/*.jpg
  • data/img/AmalgamDaikyu.jpg is excluded by !**/*.jpg
  • data/img/AmalgamEventBadge.png is excluded by !**/*.png
  • data/img/AmalgamFurax.jpg is excluded by !**/*.jpg
  • data/img/AmalgamJavlok.jpg is excluded by !**/*.jpg
  • data/img/AmalgamRipkas.jpg is excluded by !**/*.jpg
  • data/img/AmarExilusMod.jpg is excluded by !**/*.jpg
  • data/img/AmarHeader.png is excluded by !**/*.png
  • data/img/AmarMeleeMod.jpg is excluded by !**/*.jpg
  • data/img/AmarWarframeMod.jpg is excluded by !**/*.jpg
  • data/img/AmaruChromaAvatarBright.png is excluded by !**/*.png
  • data/img/AmaruChromaAvatarDark.png is excluded by !**/*.png
  • data/img/AmazingBuriGlyph.png is excluded by !**/*.png
  • data/img/AmazonOni.png is excluded by !**/*.png
  • data/img/Ambulas.png is excluded by !**/*.png
  • data/img/AmbulasDataFragment.png is excluded by !**/*.png
  • data/img/AmbulasEventBadge.png is excluded by !**/*.png
  • data/img/AmbulasEvent_e.png is excluded by !**/*.png
  • data/img/Amesha.png is excluded by !**/*.png
  • data/img/AmethystAntitoxin.png is excluded by !**/*.png
  • data/img/AmirAccoladeGlyph.png is excluded by !**/*.png
  • data/img/AmirPixelGlyph.png is excluded by !**/*.png
  • data/img/AmirValentineGlyph.png is excluded by !**/*.png
  • data/img/Amphis.png is excluded by !**/*.png
  • data/img/Amphor.png is excluded by !**/*.png
  • data/img/Amprex.png is excluded by !**/*.png
  • data/img/AmprexDayofTheDeadSkin.png is excluded by !**/*.png
  • data/img/AnJetCat.png is excluded by !**/*.png
  • data/img/AnaviIvy.png is excluded by !**/*.png
  • data/img/AngelsOfTheZarimanQuestKeychain.png is excluded by !**/*.png
  • data/img/AngelsOfTheZarimanSongItemStoreIcon.png is excluded by !**/*.png
  • data/img/AngryIceberg.png is excluded by !**/*.png
  • data/img/AngryUnicorn.png is excluded by !**/*.png
  • data/img/Angstrum.png is excluded by !**/*.png
  • data/img/AngstrumConclave.png is excluded by !**/*.png
  • data/img/AngstrumDayOfTheDead.png is excluded by !**/*.png
  • data/img/AngstrumIncarnonAdapter.png is excluded by !**/*.png
  • data/img/AnimaAlt2Helmet.png is excluded by !**/*.png
  • data/img/AnimaAspect.png is excluded by !**/*.png
  • data/img/AnimalInstincts.jpg is excluded by !**/*.jpg
  • data/img/AnimalLure.png is excluded by !**/*.png
  • data/img/AnimalTagBolarolaCommon.png is excluded by !**/*.png
  • data/img/AnimalTagBolarolaRare.png is excluded by !**/*.png
  • data/img/AnimalTagBolarolaUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagCondrocCommon.png is excluded by !**/*.png
  • data/img/AnimalTagCondrocRare.png is excluded by !**/*.png
  • data/img/AnimalTagCondrocUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagHorrasqueCommon.png is excluded by !**/*.png
  • data/img/AnimalTagHorrasqueRare.png is excluded by !**/*.png
  • data/img/AnimalTagHorrasqueUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedCritterCommon.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedCritterRare.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedCritterUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedKDriveCommon.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedKDriveRare.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedKDriveUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedMaggotCommon.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedMaggotRare.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedMaggotUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedMergooCommon.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedMergooRare.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedMergooUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedPredatorCommon.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedPredatorRare.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedPredatorUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedThwompCommon.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedThwompRare.png is excluded by !**/*.png
  • data/img/AnimalTagInfestedThwompUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagKuakaCommon.png is excluded by !**/*.png
  • data/img/AnimalTagKuakaRare.png is excluded by !**/*.png
  • data/img/AnimalTagKuakaUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagKubrodonCommon.png is excluded by !**/*.png
  • data/img/AnimalTagKubrodonRare.png is excluded by !**/*.png
  • data/img/AnimalTagKubrodonUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagMergooCommon.png is excluded by !**/*.png
  • data/img/AnimalTagMergooRare.png is excluded by !**/*.png
  • data/img/AnimalTagMergooUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagPobbersCommon.png is excluded by !**/*.png
  • data/img/AnimalTagPobbersRare.png is excluded by !**/*.png
  • data/img/AnimalTagPobbersUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagSawgawCommon.png is excluded by !**/*.png
  • data/img/AnimalTagSawgawRare.png is excluded by !**/*.png
  • data/img/AnimalTagSawgawUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagStoverCommon.png is excluded by !**/*.png
  • data/img/AnimalTagStoverRare.png is excluded by !**/*.png
  • data/img/AnimalTagStoverUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagVampireKavatCommon.png is excluded by !**/*.png
  • data/img/AnimalTagVampireKavatRare.png is excluded by !**/*.png
  • data/img/AnimalTagVampireKavatUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagVirminkCommon.png is excluded by !**/*.png
  • data/img/AnimalTagVirminkRare.png is excluded by !**/*.png
  • data/img/AnimalTagVirminkUncommon.png is excluded by !**/*.png
  • data/img/AnimalTagZongroCommon.png is excluded by !**/*.png
📒 Files selected for processing (4)
  • build/build.ts
  • build/parser.ts
  • data/cache/.export.json
  • data/cache/.images.json

Comment thread build/build.ts Outdated
Comment thread build/parser.ts
Copy link
Copy Markdown

@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

♻️ Duplicate comments (1)
build/build.ts (1)

257-264: ⚠️ Potential issue | 🟠 Major

Collision check still triggers for current item's own previous output.

The existsSync(filePath) check at line 258 fires when any file with that name exists, including the current item's output from a previous run. On incremental builds where the hash hasn't changed:

  1. processed[hash] is empty (fresh run state) → continue
  2. existsSync("texture.png") is true (from previous run)
  3. item.imageName renamed to "ItemName.png"
  4. cached?.hash === hash → download skipped
  5. "ItemName.png" never created, but JSON points to it

To only rename when a different item owns the file, add an ownership check:

-    if (existsSync(filePath)) {
+    if (existsSync(filePath) && cached?.uniqueName !== item.uniqueName) {

This skips the rename when the existing file belongs to the same item, allowing the subsequent cache check to correctly prevent unnecessary re-downloads while keeping item.imageName valid.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build/build.ts` around lines 257 - 264, The collision check renames the file
whenever it exists, even if the existing file is the same item's previous
output; update the existsSync(filePath) branch to detect ownership first (use
processed which maps hash->imageName): compute ownerHash =
Object.entries(processed).find(([, name]) => name === item.imageName)?.[0] and
only perform the rename when ownerHash is undefined or ownerHash !== hash (i.e.,
a different item owns that filename); otherwise skip renaming so cached?.hash
=== hash can correctly skip re-download and item.imageName stays valid.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@build/build.ts`:
- Around line 120-127: The sort method currently contains a leftover debug
console.log call that prints the Item when a.name is falsy; remove that
console.log(a) from the sort(a: Item, b: Item): number function so the
comparator only uses a.name.localeCompare and falls back to
a.uniqueName.localeCompare when names are equal (no other logic changes needed).

---

Duplicate comments:
In `@build/build.ts`:
- Around line 257-264: The collision check renames the file whenever it exists,
even if the existing file is the same item's previous output; update the
existsSync(filePath) branch to detect ownership first (use processed which maps
hash->imageName): compute ownerHash = Object.entries(processed).find(([, name])
=> name === item.imageName)?.[0] and only perform the rename when ownerHash is
undefined or ownerHash !== hash (i.e., a different item owns that filename);
otherwise skip renaming so cached?.hash === hash can correctly skip re-download
and item.imageName stays valid.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 866b2b98-7580-4ef3-876c-56c5afc1d8ea

📥 Commits

Reviewing files that changed from the base of the PR and between 720d881 and c7704ee.

📒 Files selected for processing (3)
  • build/build.ts
  • build/parser.ts
  • test/utilities/find.spec.mjs
✅ Files skipped from review due to trivial changes (1)
  • test/utilities/find.spec.mjs

Comment thread build/build.ts Outdated
Copy link
Copy Markdown
Member

@AyAyEm AyAyEm left a comment

Choose a reason for hiding this comment

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

Couldn't find the conflict name resolution for these items:

Octavia.png 2
Mesa.png 2
Ember.png 2
Frost.png 2
Gara.png 2
Mirage.png 2
Mag.png 2
Rhino.png 2
InarosPrime.png 2
EliteSanctuaryOnslaught.png 2
FireExtinguisher.png 2
GenericComponent.png 2
MerulinaPrime.png 2
GenericArchwingSystems.png 2
Amphis.png 2
Imperator.png 2
GenericGear.png 2
AccuracyWhileAiming.jpg 3
CritChanceWhileAiming.jpg 3
CritDamageWhileAiming.jpg 3
FireRateWhileAiming.jpg 3
StatusChanceWhileAiming.jpg 3
WeaponFireIterationsSPMod.jpg 3
WeaponStatusChanceSPMod.jpg 3
WeaponWeakpointCriticalChanceMod.jpg 2
EquinoxAgile.png 2
EquinoxNoble.png 2
AmalgamEventBadge.png 2
GarudaDeluxe.png 2
HydroidDeluxe.png 2
RhinoDeluxe.png 2
Rank10Seeker.png 2

Comment thread build/build.ts
// Check if the previous image was for a component because they might
// have different naming schemes like lex-prime
if (!cached || cached.hash !== hash || cached.isComponent !== isComponent) {
if (cached?.hash !== hash) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In which situation both values might be undefined here?

Comment thread build/build.ts Outdated
Comment on lines +120 to +144
sort(a: Item, b: Item): number {
if (!a.name) console.log(a);
const res = a.name.localeCompare(b.name);
if (res === 0) {
return a.uniqueName.localeCompare(b.uniqueName);
}
return res;
}

merge(categories: Record<string, Item[]>): Item[] {
let all: Item[] = [];

// Category names are provided by this.applyCustomCategories
for (const category of Object.keys(categories)) {
const categoryData = categories[category];
if (!categoryData) continue;
all = all.concat(categoryData);
}

// All.json (all items in one file)
all.sort(this.sort.bind(this));

return all;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this refactor necessary for the images?

Comment thread build/build.ts
@TobiTenno
Copy link
Copy Markdown
Member

@SlayerOrnstein i'm totally down for this, but the tests are failing...

@SlayerOrnstein
Copy link
Copy Markdown
Member Author

Yeah just been picking at the code a bit. Was redoing it so that saveImage didn't need to be called and trying to have a way to keep track of images with broken links

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.

3 participants