Skip to content

fix: reduce duplicate image downloads#908

Merged
TobiTenno merged 10 commits intomasterfrom
simple-image-name
Apr 29, 2026
Merged

fix: reduce duplicate image downloads#908
TobiTenno merged 10 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 across item categories
    • Enhanced detection and handling of missing images
    • Strengthened image file validation and integrity checks
  • Tests

    • Added comprehensive image existence and validity verification tests

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The build pipeline is restructured to load persisted warnings, deduplicate image filenames through a new pass, and download images per-category while updating warnings incrementally. Image naming logic is simplified: hard-coded filenames for specific items and textureLocation-based derivation for others, replacing prior hash-based collision detection. A new test suite validates image file existence, size, and parseability.

Changes

Cohort / File(s) Summary
Build pipeline refactoring
build/build.ts
Restructured to load warnings.json, seed parsed.warnings.failedImage, introduce dedupImageNames deduplication pass, and download images per-category. saveJson now returns void and writes All.json/i18n.json. saveImages refactored to accept category maps plus warnings, mark missing images as missing.png, and append failures to warnings.missingImage. Image deduplication logic moved from per-item name collision to manifest textureLocation history-based tracking with MD5 hash fallback.
Image naming logic simplification
build/parser.ts
Removed crypto SHA256 hashing and prior multi-stage filename normalization. New logic hard-codes Arcanearcane.png and Blueprintblueprint.png, derives other filenames by parsing textureLocation, and sanitizes segments. Removed Nightwave Challenge, component/relic prefix stripping, and hash-based collision handling.
Test updates
test/utilities/find.spec.mjs, test/index.spec.mjs
Updated riven-mod test expectation from hash-based filename to OmegaMod.png. New image validation test suite checks file existence, non-zero file size, and image parseability for all items with string imageName (excluding Enemy category and missing.png).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

Scope: Scraper

Suggested reviewers

  • TobiTenno
  • AyAyEm
  • EricSihaoLin

Poem

🐰 Hops past hashes, old and worn,
With dedups fresh and images reborn,
No SHA256 shadows to chase,
Just clean filenames, each in its place!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: reduce duplicate image downloads' directly aligns with the PR's main objective of reducing duplicate image downloads by improving image manifest usage and deduplication logic.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 Outdated
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
Comment thread build/build.ts Outdated
Comment thread build/build.ts Outdated
@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

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: 3

🧹 Nitpick comments (1)
test/index.spec.mjs (1)

422-443: Tests overlap: "should have the image stored on disk" already checks size > 0.

The first test (lines 422-433) checks both existence and size. The second test (lines 435-443) duplicates the size check. Consider consolidating or removing the redundant test.

♻️ Suggested consolidation
       it('should have the image stored on disk', () => {
         for (const { imageName, uniqueName } of imageNames) {
           if(imageName === 'missing.png') continue;
           const imagePath = path.join('./data/img/', imageName);
           const nameInfo = `in { uniqueName: ${uniqueName}, imageName: ${imageName} }`;
-          let exists = existsSync(imagePath);
-          assert(exists, `${imageName} should exist ${nameInfo}`);
-          if (exists) {
-            const { size } = statSync(imagePath, () => {});
-            assert(size > 0, `size should be greater than zero ${nameInfo}`);
-          }
+          assert(existsSync(imagePath), `${imageName} should exist ${nameInfo}`);
+          const { size } = statSync(imagePath);
+          assert(size > 0, `size should be greater than zero ${nameInfo}`);
         }
       });
-      it('size should be greater than 0', () => {
-        for (const { imageName, uniqueName } of imageNames) {
-          if(imageName === 'missing.png') continue;
-          const imagePath = path.join('./data/img/', imageName);
-          const nameInfo = `in { uniqueName: ${uniqueName}, imageName: ${imageName} }`;
-          const { size } = statSync(imagePath, () => {});
-          assert(size > 0, `size should be greater than zero ${nameInfo}`);
-        }
-      });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/index.spec.mjs` around lines 422 - 443, The second test "size should be
greater than 0" duplicates the size assertion already performed in "should have
the image stored on disk"; remove the redundant test or merge its assertions
into the first test: keep the loop over imageNames (skipping 'missing.png'),
verify file existence with existsSync and then check size > 0 using statSync in
the same test (symbols: tests named "should have the image stored on disk" and
"size should be greater than 0", imageNames array, path.join('./data/img/',
imageName), existsSync, statSync, assert).
🤖 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`:
- Line 386: The comparator passed to group.sort is indexing priorityMap with
Item objects (a and b) which yields undefined; change it to index using the
Item's category string property (e.g., use a.category or the actual category
field used on Item) so the comparison becomes (priorityMap[a.category] ??
Infinity) - (priorityMap[b.category] ?? Infinity); update the group.sort call
accordingly to reference the correct category key from the Item objects to
restore priority-based ordering and deduplication.

In `@test/index.spec.mjs`:
- Line 440: The test is calling fs.statSync with a callback (const { size } =
statSync(imagePath, () => {})), but statSync is synchronous and accepts no
callback; remove the callback and call statSync(imagePath) (or switch to an
async stat/fs.promises.stat if you intended async behavior) so that the
destructuring const { size } = statSync(imagePath) works correctly; update the
reference in the test where statSync and imagePath are used.
- Line 430: The call to statSync incorrectly passes a callback
(statSync(imagePath, () => {})); remove the second argument so the synchronous
fs.statSync is used properly: call statSync with only the path (referenced
symbol: statSync and variable imagePath) and keep extracting { size } from its
returned Stats object.

---

Nitpick comments:
In `@test/index.spec.mjs`:
- Around line 422-443: The second test "size should be greater than 0"
duplicates the size assertion already performed in "should have the image stored
on disk"; remove the redundant test or merge its assertions into the first test:
keep the loop over imageNames (skipping 'missing.png'), verify file existence
with existsSync and then check size > 0 using statSync in the same test
(symbols: tests named "should have the image stored on disk" and "size should be
greater than 0", imageNames array, path.join('./data/img/', imageName),
existsSync, statSync, assert).
🪄 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: 8bdff327-626f-40d8-acd2-2c67182a3ac6

📥 Commits

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

⛔ 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

Comment thread build/build.ts Outdated
Comment thread test/index.spec.mjs Outdated
Comment thread test/index.spec.mjs Outdated
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: 3

🤖 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 181-182: The code currently uses history: string[] to record seen
hashes which causes duplicates to return without adopting an existing written
filename and also marks a hash seen before the write succeeds; change history to
a Map<string,string> (or object) mapping hash -> successful filename, only
insert into that map after the file is written successfully, and when
encountering an already-seen hash lookup and return the mapped filename so later
resources reuse the first successful filename (update logic around the block
that reads/writes imageName and where history is pushed).
- Line 66: The catch currently wraps a parent image and its nested
components/abilities so when a nested asset fails we only set the parent to
missing.png and then restore parsed.warnings.failedImage =
[...warnings.failedImage], which overwrites and drops newly-detected failures;
instead scope failures to the exact asset that threw, push that asset's
identifier onto parsed.warnings.failedImage (or merge warnings.failedImage with
parsed.warnings.failedImage) at the point of the catch, and ensure the code that
sets the parent to "missing.png" does not short-circuit processing of other
nested assets so all individual failures get persisted for retry.
- Line 43: Wrap the call to readJson in a try/catch and default to an empty
Warnings shape when the file is missing: call readJson<Warnings>(...) inside a
try block, assign its result to the warnings variable, and in the catch check
for a "file not found" error (e.g., ENOENT) or otherwise fallback to an empty
object of type Warnings; update any code that uses the warnings variable
accordingly so build continues when warnings.json is absent.
🪄 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: 2dd56305-f7f8-4c66-b277-f981b0fd0692

📥 Commits

Reviewing files that changed from the base of the PR and between f3c27c6 and 6052745.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (21)
  • .github/workflows/build.yaml
  • .github/workflows/release.yaml
  • .github/workflows/static.yaml
  • README.md
  • build/build.ts
  • data/cache/.export.json
  • data/json/All.json
  • data/json/Arch-Gun.json
  • data/json/Arch-Melee.json
  • data/json/Melee.json
  • data/json/Misc.json
  • data/json/Pets.json
  • data/json/Primary.json
  • data/json/Relics.json
  • data/json/Resources.json
  • data/json/Secondary.json
  • data/json/SentinelWeapons.json
  • data/json/Skins.json
  • data/json/Warframes.json
  • data/json/i18n.json
  • package.json
✅ Files skipped from review due to trivial changes (6)
  • package.json
  • .github/workflows/build.yaml
  • .github/workflows/release.yaml
  • .github/workflows/static.yaml
  • README.md
  • data/cache/.export.json

Comment thread build/build.ts
Comment thread build/build.ts
Comment thread build/build.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: 2

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

43-43: ⚠️ Potential issue | 🟠 Major

Handle a missing warnings.json here.

Line 43 makes the build depend on a previously generated artifact. On a clean checkout, this can fail before any work starts. Please fall back to an empty Warnings object instead of hard-failing.

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

In `@build/build.ts` at line 43, The build currently crashes if warnings.json is
missing; wrap the readJson<Warnings>(new URL('../data/warnings.json',
import.meta.url)) call in a try/catch and, on failure (or specifically if the
error indicates file-not-found), set warnings to an empty Warnings object (e.g.,
{} as Warnings) instead of throwing; update the code that references warnings to
expect this fallback.

187-205: ⚠️ Potential issue | 🟠 Major

Catch failures per asset, not per parent item.

Lines 188-205 wrap the parent image plus every component and ability together. If one nested download throws, the parent gets missing.png, the remaining nested assets are skipped, and the failing asset never lands in warnings.failedImage, so it is lost on the next run.

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

In `@build/build.ts` around lines 187 - 205, The current try/catch around the
whole parent/item block causes one nested failure to mark the entire parent as
missing and skip remaining assets; instead move error handling to each call to
saveImage so failures are caught per asset: wrap the calls to
this.saveImage(item,...), each this.saveImage(component,...), and each
this.saveImage(ability...) in their own try/catch; in each catch push the
specific asset name into warnings.failedImage (and only push to
warnings.missingImage or set item.imageName = 'missing.png' for the parent item
when the parent image itself fails), and continue processing other
components/abilities so no nested asset is skipped.

242-246: ⚠️ Potential issue | 🔴 Critical

Only mark an image as processed after a successful write.

Lines 245-246 add imageBase.textureLocation to history before the file is downloaded and written. If that first attempt fails, later items sharing the same resource return early and keep an imageName that was never written to disk.

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

In `@build/build.ts` around lines 242 - 246, The code prematurely marks
imageBase.textureLocation as processed (history.push) before the download/write
completes; change the flow so you only add imageBase.textureLocation to history
after the file is successfully written (or write+verification succeeds) and
ensure the early return that checks history (the if that includes
history.includes(imageBase.textureLocation)) remains but only short-circuits
after a successful previous write; update the logic around the handling of
item.name ('Blueprint'/'Arcane') and the write routine so that
history.push(imageBase.textureLocation) is moved to the post-write success path
(or called in the write-success callback/promise resolution).

134-136: ⚠️ Potential issue | 🟡 Minor

Remove the leftover debug log.

Line 135 still prints the whole item during sorting when a.name is falsy. That adds noisy build output without affecting behavior.

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

In `@build/build.ts` around lines 134 - 136, The sort comparator function sort(a:
Item, b: Item) contains a leftover debug console.log(a) when a.name is falsy;
remove that console.log and, if needed, handle missing names deterministically
(e.g., treat empty/null names consistently before calling
a.name.localeCompare(b.name)) so the comparator doesn't produce noisy build
output.
🤖 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 368-372: dedupImageNames is deriving image identity from
textureLocation by blindly splitting on '!' whereas saveImage() falls back to
the fileTime/MD5 identity when no '!hash' is present; update dedupImageNames
(the manifest.reduce that builds hashMap and the collision-resolution logic
around the block handling entries at the later collision code path) to reuse the
exact same identity logic as saveImage(): if textureLocation contains a '!' use
the suffix after '!' as the identity, otherwise compute/derive the fallback
identity (fileTime or MD5 fallback used by saveImage()) and use that for hashMap
lookup and for dedup checks so two items missing '!hash' are treated distinctly
unless their fallback identity matches. Ensure you reference and reuse the same
helper or logic that saveImage() uses for generating the fallback identity to
keep behavior consistent.
- Around line 404-410: The current fallback rename for collisions uses only
item.category causing different items in the same category to get identical
names; update the renaming logic in the loop that references seen, group,
mainItem, hashMap, imageName and item.imageName so the fallback includes an
item-specific discriminator (for example append a deterministic short token
derived from item.uniqueName or an index/sequence) before the extension — e.g.
compute a safe shortId from item.uniqueName (or use the loop index) and build
item.imageName as `${stem}${item.category}-${shortId}${ext}` (ensure the shortId
is filesystem-safe and deterministic so repeated runs are stable).

---

Duplicate comments:
In `@build/build.ts`:
- Line 43: The build currently crashes if warnings.json is missing; wrap the
readJson<Warnings>(new URL('../data/warnings.json', import.meta.url)) call in a
try/catch and, on failure (or specifically if the error indicates
file-not-found), set warnings to an empty Warnings object (e.g., {} as Warnings)
instead of throwing; update the code that references warnings to expect this
fallback.
- Around line 187-205: The current try/catch around the whole parent/item block
causes one nested failure to mark the entire parent as missing and skip
remaining assets; instead move error handling to each call to saveImage so
failures are caught per asset: wrap the calls to this.saveImage(item,...), each
this.saveImage(component,...), and each this.saveImage(ability...) in their own
try/catch; in each catch push the specific asset name into warnings.failedImage
(and only push to warnings.missingImage or set item.imageName = 'missing.png'
for the parent item when the parent image itself fails), and continue processing
other components/abilities so no nested asset is skipped.
- Around line 242-246: The code prematurely marks imageBase.textureLocation as
processed (history.push) before the download/write completes; change the flow so
you only add imageBase.textureLocation to history after the file is successfully
written (or write+verification succeeds) and ensure the early return that checks
history (the if that includes history.includes(imageBase.textureLocation))
remains but only short-circuits after a successful previous write; update the
logic around the handling of item.name ('Blueprint'/'Arcane') and the write
routine so that history.push(imageBase.textureLocation) is moved to the
post-write success path (or called in the write-success callback/promise
resolution).
- Around line 134-136: The sort comparator function sort(a: Item, b: Item)
contains a leftover debug console.log(a) when a.name is falsy; remove that
console.log and, if needed, handle missing names deterministically (e.g., treat
empty/null names consistently before calling a.name.localeCompare(b.name)) so
the comparator doesn't produce noisy build output.
🪄 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: a3a9439e-58b7-43cc-8f8f-ac92297ffbfa

📥 Commits

Reviewing files that changed from the base of the PR and between 6052745 and 55e134e.

⛔ 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 (4)
  • build/build.ts
  • build/parser.ts
  • test/index.spec.mjs
  • test/utilities/find.spec.mjs
✅ Files skipped from review due to trivial changes (1)
  • test/utilities/find.spec.mjs
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/index.spec.mjs

Comment thread build/build.ts
Comment thread build/build.ts
Signed-off-by: SlayerOrnstein <6075693+SlayerOrnstein@users.noreply.github.com>
Signed-off-by: SlayerOrnstein <6075693+SlayerOrnstein@users.noreply.github.com>
Signed-off-by: SlayerOrnstein <6075693+SlayerOrnstein@users.noreply.github.com>
Signed-off-by: SlayerOrnstein <6075693+SlayerOrnstein@users.noreply.github.com>
Signed-off-by: SlayerOrnstein <6075693+SlayerOrnstein@users.noreply.github.com>
Signed-off-by: SlayerOrnstein <6075693+SlayerOrnstein@users.noreply.github.com>
@TobiTenno TobiTenno merged commit 3316c3f into master Apr 29, 2026
10 checks passed
@TobiTenno TobiTenno deleted the simple-image-name branch April 29, 2026 04:24
@ketoweb
Copy link
Copy Markdown

ketoweb commented Apr 29, 2026

@SlayerOrnstein @TobiTenno can a new build be committed or will the bot handle it? The images are now broken until the newly merged PR takes effect?

@SlayerOrnstein
Copy link
Copy Markdown
Member Author

@ketoweb fixing it now

@wfcd-bot-boi
Copy link
Copy Markdown
Collaborator

🎉 This PR is included in version 1.1274.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants