From 8d467898af65c701d3a0d4168b2dd205583b4554 Mon Sep 17 00:00:00 2001 From: Gamaliel Date: Tue, 27 Jan 2026 12:55:10 -0400 Subject: [PATCH 01/10] fix(data): update card damage and hitpoints values in cards.json Update tower damage and standard damage for a specific card, significantly increasing values across levels 11, 15, and 16. Additionally, apply minor hitpoint adjustments to another card to ensure data accuracy. --- cards.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cards.json b/cards.json index b2f0443..24dbc3a 100644 --- a/cards.json +++ b/cards.json @@ -6505,14 +6505,14 @@ "level16": null }, "towerDamage": { - "level11": 31, - "level15": 45, - "level16": 49 + "level11": 93, + "level15": 135, + "level16": 147 }, "damage": { - "level11": 123, - "level15": 179, - "level16": 196 + "level11": 366, + "level15": 534, + "level16": 588 }, "hitpoints": { "level11": null, @@ -7631,9 +7631,9 @@ "level16": 699 }, "hitpoints": { - "level11": 547, + "level11": 548, "level15": 796, - "level16": 874 + "level16": 875 }, "statsEvo": { "cycles": null, From f4f7652dc1ceb4df6717d2b6dc2d26454ec2d389 Mon Sep 17 00:00:00 2001 From: Gamaliel Date: Tue, 27 Jan 2026 12:56:55 -0400 Subject: [PATCH 02/10] fix(data): update unit generation stats in cards.json - Set generationUnits to 2 for an entry previously set to null. - Decreased generationSpeed for two card entries (15.0 to 14.0 and 4.0 to 3.5). - These adjustments tune the spawn rates and counts for specific units. --- cards.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cards.json b/cards.json index 24dbc3a..5655438 100644 --- a/cards.json +++ b/cards.json @@ -3985,7 +3985,7 @@ "hitspeed": 1.5, "radius": null, "generationSpeed": null, - "generationUnits": null, + "generationUnits": 2, "speed": "fast", "range": 1.2, "territory": "restricted", @@ -5948,7 +5948,7 @@ }, "hitspeed": 10.0, "radius": null, - "generationSpeed": 15.0, + "generationSpeed": 14.0, "generationUnits": 3, "speed": null, "range": null, @@ -6208,7 +6208,7 @@ }, "hitspeed": 10.0, "radius": null, - "generationSpeed": 4.0, + "generationSpeed": 3.5, "generationUnits": 2, "speed": null, "range": 2.0, From d51f7db33c25a4d0ce2ada3c7e3d227f7c1577c7 Mon Sep 17 00:00:00 2001 From: Gamaliel Date: Tue, 27 Jan 2026 12:59:15 -0400 Subject: [PATCH 03/10] fix(data): update projectile property for multiple card entries Updates the "projectile" field from false to true for several cards in cards.json. This correction ensures that cards with projectile-based attacks are accurately represented in the dataset. --- cards.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cards.json b/cards.json index 5655438..ff5750a 100644 --- a/cards.json +++ b/cards.json @@ -1516,7 +1516,7 @@ "evolution": false, "hero": false, "typeAttack": "splash", - "projectile": false, + "projectile": true, "suicide": false, "fatalDamage": { "level11": null, @@ -2695,7 +2695,7 @@ "evolution": false, "hero": false, "typeAttack": "unique", - "projectile": false, + "projectile": true, "suicide": false, "fatalDamage": { "level11": null, @@ -3483,7 +3483,7 @@ "evolution": false, "hero": false, "typeAttack": "unique", - "projectile": false, + "projectile": true, "suicide": false, "fatalDamage": { "level11": null, @@ -5315,7 +5315,7 @@ "evolution": false, "hero": false, "typeAttack": "unique", - "projectile": false, + "projectile": true, "suicide": false, "fatalDamage": { "level11": null, @@ -6885,7 +6885,7 @@ "evolution": false, "hero": false, "typeAttack": "unique", - "projectile": false, + "projectile": true, "suicide": false, "fatalDamage": { "level11": null, @@ -7608,7 +7608,7 @@ "evolution": false, "hero": false, "typeAttack": "splash", - "projectile": false, + "projectile": true, "suicide": false, "fatalDamage": { "level11": null, From 3ebb058d45979a7e90f1c8029d44a9632588aea5 Mon Sep 17 00:00:00 2001 From: Gamaliel Date: Tue, 27 Jan 2026 13:01:06 -0400 Subject: [PATCH 04/10] fix(data): update movement speed values for multiple cards in cards.json Several card entries in the cards.json file have had their speed property updated to better reflect current balance adjustments. This includes changes across various rarities (common, rare, epic, legendary, champion) to refine the movement mechanics of the units. --- cards.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/cards.json b/cards.json index ff5750a..5453adb 100644 --- a/cards.json +++ b/cards.json @@ -712,7 +712,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "very-fast", + "speed": "fast", "range": 0.5, "territory": "restricted", "rarity": "common", @@ -842,7 +842,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "very-fast", + "speed": "fast", "range": 0.5, "territory": "restricted", "rarity": "epic", @@ -1366,7 +1366,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "slow", + "speed": "medium", "range": 0.8, "territory": "restricted", "rarity": "epic", @@ -2283,7 +2283,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "medium", + "speed": "slow", "range": 4.0, "territory": "restricted", "rarity": "epic", @@ -2808,7 +2808,7 @@ "radius": 3.0, "generationSpeed": null, "generationUnits": null, - "speed": "medium", + "speed": "fast", "range": 5.0, "territory": "restricted", "rarity": "legendary", @@ -2873,7 +2873,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "very-fast", + "speed": "fast", "range": 1.2, "territory": "restricted", "rarity": "common", @@ -2939,7 +2939,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "slow", + "speed": "medium", "range": 4.0, "territory": "restricted", "rarity": "epic", @@ -3070,7 +3070,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "very-fast", + "speed": "fast", "range": 0.75, "territory": "restricted", "rarity": "legendary", @@ -3200,7 +3200,7 @@ "radius": null, "generationSpeed": 5.0, "generationUnits": 2, - "speed": "fast", + "speed": "medium", "range": 1.6, "territory": "restricted", "rarity": "legendary", @@ -3725,7 +3725,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "very-fast", + "speed": "fast", "range": null, "territory": "restricted", "rarity": "common", @@ -3791,7 +3791,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "medium", + "speed": "fast", "range": 6.0, "territory": "restricted", "rarity": "rare", @@ -3921,7 +3921,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "fast", + "speed": "very-fast", "range": 0.75, "territory": "restricted", "rarity": "rare", @@ -3986,7 +3986,7 @@ "radius": null, "generationSpeed": null, "generationUnits": 2, - "speed": "fast", + "speed": "medium", "range": 1.2, "territory": "restricted", "rarity": "epic", @@ -4249,7 +4249,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "medium", + "speed": "fast", "range": 6.0, "territory": "restricted", "rarity": "common", @@ -4575,7 +4575,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "fast", + "speed": "medium", "range": 5.0, "territory": "restricted", "rarity": "champion", @@ -5034,7 +5034,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "very-fast", + "speed": "medium", "range": 1.6, "territory": "restricted", "rarity": "legendary", @@ -6276,7 +6276,7 @@ "radius": null, "generationSpeed": 7.0, "generationUnits": 1, - "speed": "slow", + "speed": "medium", "range": 6.0, "territory": "restricted", "rarity": "rare", @@ -6407,7 +6407,7 @@ "radius": null, "generationSpeed": null, "generationUnits": null, - "speed": "fast", + "speed": "medium", "range": null, "territory": "wide", "rarity": "epic", From 78f59aebe728944ff49772fc924e9f2f0dd6589d Mon Sep 17 00:00:00 2001 From: Gamaliel Date: Tue, 27 Jan 2026 13:03:39 -0400 Subject: [PATCH 05/10] fix(data): update radius values and attack type classification - Replaced null values with specific numerical radius data across multiple card entries in cards.json. - Updated typeAttack from "unique" to "splash" for specific card instances. - This change provides accurate area-of-effect metadata for the affected cards. --- cards.json | 68 +++++++++++++++++++++++++++--------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/cards.json b/cards.json index 5453adb..70986fd 100644 --- a/cards.json +++ b/cards.json @@ -514,7 +514,7 @@ "prestigeCost": null }, "hitspeed": 1.1, - "radius": null, + "radius": 1.5, "generationSpeed": 7.0, "generationUnits": 4, "speed": "medium", @@ -774,7 +774,7 @@ "prestigeCost": null }, "hitspeed": 1.5, - "radius": null, + "radius": 2.0, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -904,7 +904,7 @@ "prestigeCost": null }, "hitspeed": 1.8, - "radius": null, + "radius": 1.5, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -1036,7 +1036,7 @@ "prestigeCost": null }, "hitspeed": 1.5, - "radius": null, + "radius": 1.5, "generationSpeed": null, "generationUnits": null, "speed": "fast", @@ -1167,7 +1167,7 @@ "prestigeCost": 1 }, "hitspeed": 1.4, - "radius": null, + "radius": 1.5, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -1756,7 +1756,7 @@ "prestigeCost": null }, "hitspeed": 3.0, - "radius": null, + "radius": 2.5, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -1821,7 +1821,7 @@ "prestigeCost": null }, "hitspeed": 1.3, - "radius": null, + "radius": 1.1, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -2018,7 +2018,7 @@ "prestigeCost": null }, "hitspeed": 0.3, - "radius": null, + "radius": 1.5, "generationSpeed": null, "generationUnits": null, "speed": "very-fast", @@ -2084,7 +2084,7 @@ "prestigeCost": null }, "hitspeed": 0.3, - "radius": null, + "radius": 2.3, "generationSpeed": null, "generationUnits": null, "speed": "very-fast", @@ -2215,7 +2215,7 @@ "prestigeCost": null }, "hitspeed": 4.0, - "radius": null, + "radius": 1.8, "generationSpeed": null, "generationUnits": null, "speed": "slow", @@ -2280,7 +2280,7 @@ "prestigeCost": null }, "hitspeed": 2.5, - "radius": null, + "radius": 1.8, "generationSpeed": null, "generationUnits": null, "speed": "slow", @@ -2760,7 +2760,7 @@ "duration": 0.001, "evolution": false, "hero": false, - "typeAttack": "unique", + "typeAttack": "splash", "projectile": false, "suicide": false, "fatalDamage": { @@ -2936,7 +2936,7 @@ "prestigeCost": null }, "hitspeed": 2.2, - "radius": null, + "radius": 0.07, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -3002,7 +3002,7 @@ "prestigeCost": null }, "hitspeed": 0.9, - "radius": null, + "radius": 1.0, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -3328,7 +3328,7 @@ "prestigeCost": null }, "hitspeed": 1.8, - "radius": null, + "radius": 1.0, "generationSpeed": null, "generationUnits": null, "speed": "fast", @@ -3657,7 +3657,7 @@ "prestigeCost": null }, "hitspeed": 1.7, - "radius": null, + "radius": 1.3, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -3853,7 +3853,7 @@ "prestigeCost": null }, "hitspeed": 1.2, - "radius": null, + "radius": 1.5, "generationSpeed": null, "generationUnits": null, "speed": "very-fast", @@ -4114,7 +4114,7 @@ "prestigeCost": null }, "hitspeed": 1.1, - "radius": null, + "radius": 0.25, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -4396,7 +4396,7 @@ "duration": null, "evolution": false, "hero": false, - "typeAttack": "unique", + "typeAttack": "splash", "projectile": false, "suicide": false, "fatalDamage": { @@ -4506,7 +4506,7 @@ "prestigeCost": null }, "hitspeed": 1.6, - "radius": null, + "radius": 1.3, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -4768,7 +4768,7 @@ "prestigeCost": null }, "hitspeed": 1.9, - "radius": null, + "radius": 0.8, "generationSpeed": null, "generationUnits": null, "speed": "fast", @@ -5161,8 +5161,8 @@ "statsHero": { "prestigeCost": null }, - "hitspeed": 1.2, - "radius": null, + "hitspeed": 1.1, + "radius": 1.5, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -5751,7 +5751,7 @@ "prestigeCost": null }, "hitspeed": 5.0, - "radius": null, + "radius": 2.0, "generationSpeed": null, "generationUnits": null, "speed": null, @@ -5882,7 +5882,7 @@ "prestigeCost": null }, "hitspeed": 1.8, - "radius": null, + "radius": 1.5, "generationSpeed": null, "generationUnits": null, "speed": null, @@ -6031,7 +6031,7 @@ "duration": 65.0, "evolution": false, "hero": false, - "typeAttack": null, + "typeAttack": "unique", "projectile": false, "suicide": false, "fatalDamage": { @@ -6689,7 +6689,7 @@ "duration": null, "evolution": true, "hero": false, - "typeAttack": "unique", + "typeAttack": "splash", "projectile": true, "suicide": false, "fatalDamage": { @@ -6818,7 +6818,7 @@ "duration": null, "evolution": false, "hero": false, - "typeAttack": null, + "typeAttack": "unique", "projectile": false, "suicide": false, "fatalDamage": { @@ -6884,7 +6884,7 @@ "duration": 1.5, "evolution": false, "hero": false, - "typeAttack": "unique", + "typeAttack": "splash", "projectile": true, "suicide": false, "fatalDamage": { @@ -7082,7 +7082,7 @@ "duration": 9.0, "evolution": false, "hero": false, - "typeAttack": "unique", + "typeAttack": "splash", "projectile": false, "suicide": false, "fatalDamage": { @@ -7192,7 +7192,7 @@ "prestigeCost": null }, "hitspeed": null, - "radius": null, + "radius": 1.95, "generationSpeed": null, "generationUnits": null, "speed": null, @@ -7279,7 +7279,7 @@ "duration": 1.0, "evolution": false, "hero": false, - "typeAttack": null, + "typeAttack": "splash", "projectile": false, "suicide": false, "fatalDamage": { @@ -7454,7 +7454,7 @@ "prestigeCost": null }, "hitspeed": 1.3, - "radius": null, + "radius": 1.3, "generationSpeed": null, "generationUnits": null, "speed": "medium", @@ -7520,7 +7520,7 @@ "prestigeCost": null }, "hitspeed": 0.3, - "radius": null, + "radius": 1.5, "generationSpeed": null, "generationUnits": null, "speed": "very-fast", From 3014fa43d12b88e72beac33ddedd9f4d2e404c36 Mon Sep 17 00:00:00 2001 From: Gamaliel Date: Tue, 27 Jan 2026 13:04:55 -0400 Subject: [PATCH 06/10] fix(data): reduce unit counts for multiple card entries Update the units property for several card definitions in cards.json, reducing values from 7 and 2 down to 1. This ensures the data correctly reflects the unit count for specific building-targeting and air/ground cards, including those with evolutions. --- cards.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cards.json b/cards.json index 70986fd..1ceb956 100644 --- a/cards.json +++ b/cards.json @@ -3673,7 +3673,7 @@ "targets": [ "buildings" ], - "units": 7, + "units": 1, "duration": null, "evolution": true, "hero": false, @@ -5048,7 +5048,7 @@ "air", "ground" ], - "units": 2, + "units": 1, "duration": null, "evolution": false, "hero": false, @@ -5243,7 +5243,7 @@ "targets": [ "buildings" ], - "units": 2, + "units": 1, "duration": 1.0, "evolution": false, "hero": false, From 1e96609789851711b3f2aec2c919305308fd67f0 Mon Sep 17 00:00:00 2001 From: Gamaliel Date: Tue, 27 Jan 2026 13:07:40 -0400 Subject: [PATCH 07/10] build(update-cards): improve card data extraction in update-cards.js Enhance the script to handle more complex card attributes and multi-unit data from the API. Key changes include: - Added normalization maps for movement speed and target types. - Improved target extraction by scanning nested character and projectile data. - Added support for aggregating unit counts and stats across multiple summoned character types. - Refined damage and HP detection logic to better handle area effects and secondary projectiles. - Improved error reporting during API response parsing. --- scripts/update-cards.js | 136 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 124 insertions(+), 12 deletions(-) diff --git a/scripts/update-cards.js b/scripts/update-cards.js index dc3321f..790894c 100644 --- a/scripts/update-cards.js +++ b/scripts/update-cards.js @@ -52,6 +52,22 @@ const CARD_SKELETON = { type: null }; +const SPEED_MAP = { + 'TID_SPEED_0': 'slow', + 'TID_SPEED_1': 'slow', + 'TID_SPEED_2': 'slow', + 'TID_SPEED_3': 'medium', + 'TID_SPEED_4': 'fast', + 'TID_SPEED_5': 'very-fast' +}; + +const TARGETS_MAP = { + 'TID_TARGETS_GROUND': ['ground'], + 'TID_TARGETS_AIR_AND_GROUND': ['air', 'ground'], + 'TID_TARGETS_BUILDINGS': ['buildings'], + 'TID_TARGETS_NONE': [] +}; + /** * Fetch JSON data from URL */ @@ -64,7 +80,7 @@ function fetchData(url) { try { resolve(JSON.parse(data)); } catch (e) { - reject(new Error('Failed to parse API response')); + reject(new Error('Failed to parse API response: ' + e.message)); } }); }).on('error', (err) => reject(err)); @@ -137,30 +153,110 @@ async function main() { // Update basic info if missing or new card.elixirCost = apiItem.manaCost ?? card.elixirCost; + + const extractTargets = (tid) => TARGETS_MAP[tid] || []; + const allTargets = [ + ...extractTargets(apiItem.tidTarget), + ...extractTargets(apiItem.projectileData?.tidTarget), + ...extractTargets(apiItem.summonCharacterData?.tidTarget), + ...extractTargets(apiItem.summonCharacterData?.spawnCharacterData?.tidTarget), + ...extractTargets(apiItem.summonCharacterSecondData?.tidTarget), + ...extractTargets(apiItem.summonCharacterSecondData?.spawnCharacterData?.tidTarget), + ...extractTargets(apiItem.summonCharacterThirdData?.tidTarget), + ...extractTargets(apiItem.summonCharacterThirdData?.spawnCharacterData?.tidTarget) + ]; + card.targets = [...(new Set(allTargets).length > 0 ? new Set(allTargets) : card.targets)]; card.rarity = (apiItem.rarity || card.rarity || '').toLowerCase(); + card.units = (apiItem.summonNumber || 0) + (apiItem.summonCharacterSecondCount || 0) + (apiItem.summonCharacterThirdCount || 0) || card.units; // Extract base stats + // Handle multiple characters + const characters = []; + if (apiItem.summonCharacterData) characters.push({ data: apiItem.summonCharacterData, count: apiItem.summonNumber || 1 }); + if (apiItem.summonCharacterSecondData) characters.push({ data: apiItem.summonCharacterSecondData, count: apiItem.summonCharacterSecondCount || 1 }); + if (apiItem.summonCharacterThirdData) characters.push({ data: apiItem.summonCharacterThirdData, count: apiItem.summonCharacterThirdCount || 1 }); + + // fallback charData for single-unit logic or backward compatibility let charData = apiItem.summonCharacterData || apiItem.statCharacterData || {}; - // Handle E-Wiz and similar cases where character data is inside area effect + // Handle cases where character data is inside area effect if (Object.keys(charData).length === 0 && apiItem.areaEffectObjectData && apiItem.areaEffectObjectData.onStartingActionData && apiItem.areaEffectObjectData.onStartingActionData.spawnDataData) { charData = apiItem.areaEffectObjectData.onStartingActionData.spawnDataData; } - const projData = apiItem.projectileData || (charData.projectileData) || {}; const areaData = apiItem.areaEffectObjectData || {}; + const projData = apiItem.projectileData || charData.projectileData || areaData.projectileData || {}; const buffData = areaData.buffData || {}; const spawnProjData = projData.spawnProjectileData || areaData.projectileData || {}; const spawnCharData = projData.spawnCharacterData || areaData.spawnCharacterData || {}; - const baseHP = charData.hitpoints || spawnCharData.hitpoints || null; - const baseDamage = charData.damage || projData.damage || areaData.damage || buffData.damagePerSecond || spawnProjData.damage || spawnCharData.damage || null; + // Aggregation logic for multi-units + let maxHP = 0; + let primaryChar = charData; + let totalDamage = 0; + + if (characters.length > 0) { + characters.forEach(c => { + const d = c.data; + const hp = d.hitpoints || 0; + const p = d.projectileData || {}; + const unitDamage = d.damage || p.damage || 0; + + if (hp > maxHP) { + maxHP = hp; + primaryChar = d; + } + + if (unitDamage > totalDamage) { + totalDamage = unitDamage; + } + + if (Object.keys(p).length > 0) { + card.projectile = true; + } + }); + + card.hitspeed = (primaryChar.hitSpeed ? primaryChar.hitSpeed / 1000 : card.hitspeed); + card.range = (primaryChar.range ? primaryChar.range / 1000 : card.range); + card.speed = SPEED_MAP[primaryChar.tidSpeed] || card.speed; + } else { + card.hitspeed = (charData.hitSpeed ? charData.hitSpeed / 1000 : card.hitspeed); + card.range = (charData.range ? charData.range / 1000 : card.range); + card.speed = SPEED_MAP[charData.tidSpeed] || card.speed; + } + + card.generationSpeed = (charData.spawnPauseTime ? charData.spawnPauseTime / 1000 : card.generationSpeed); + card.generationUnits = ((charData.spawnNumber && charData.spawnNumber > 1) ? charData.spawnNumber : card.generationUnits); + + // Radius and Type Attack + const rawRadius = apiItem.radius ?? areaData.radius ?? charData.areaDamageRadius ?? projData.radius ?? + (projData.customFirstProjectileData ? projData.customFirstProjectileData.radius : null); + if (rawRadius !== null && rawRadius !== undefined) { + card.radius = rawRadius / 1000; + card.typeAttack = 'splash'; + } else { + card.typeAttack = card.typeAttack || 'unique'; + } + + // Projectile + card.projectile = (Object.keys(projData).length > 0 || card.projectile); + + // Life duration / Duration + const rawDuration = apiItem.lifeTime ?? areaData.lifeDuration ?? null; + if (rawDuration !== null) { + card.duration = rawDuration / 1000; + } + + const baseHP = maxHP || charData.hitpoints || spawnCharData.hitpoints || null; + const baseDamage = totalDamage || charData.damage || projData.damage || areaData.damage || buffData.damagePerSecond || + spawnProjData.damage || spawnCharData.damage || null; const baseFatal = charData.deathDamage || (charData.deathSpawnCharacterData ? charData.deathSpawnCharacterData.deathDamage : null) || null; const baseCharge = charData.damageSpecial || null; // Tower damage calculation for spells/troops let baseTowerDamage = null; - const towerDamagePercent = apiItem.crownTowerDamagePercent ?? charData.crownTowerDamagePercent ?? projData.crownTowerDamagePercent ?? areaData.crownTowerDamagePercent ?? buffData.crownTowerDamagePercent ?? spawnProjData.crownTowerDamagePercent ?? spawnCharData.crownTowerDamagePercent; + const towerDamagePercent = apiItem.crownTowerDamagePercent ?? charData.crownTowerDamagePercent ?? projData.crownTowerDamagePercent ?? + areaData.crownTowerDamagePercent ?? buffData.crownTowerDamagePercent ?? spawnProjData.crownTowerDamagePercent ?? spawnCharData.crownTowerDamagePercent; if (towerDamagePercent !== undefined && baseDamage) { baseTowerDamage = baseDamage * (100 + towerDamagePercent) / 100; } @@ -204,7 +300,8 @@ async function main() { const evoData = apiItem.evolvedSpellsData; let evoCharData = evoData.summonCharacterData || {}; - if (Object.keys(evoCharData).length === 0 && evoData.areaEffectObjectData && evoData.areaEffectObjectData.onStartingActionData && evoData.areaEffectObjectData.onStartingActionData.spawnDataData) { + if (Object.keys(evoCharData).length === 0 && evoData.areaEffectObjectData && evoData.areaEffectObjectData.onStartingActionData && + evoData.areaEffectObjectData.onStartingActionData.spawnDataData) { evoCharData = evoData.areaEffectObjectData.onStartingActionData.spawnDataData; } @@ -215,7 +312,8 @@ async function main() { const evoSpawnCharData = evoProjData.spawnCharacterData || evoAreaData.spawnCharacterData || {}; const baseEvoHP = evoCharData.hitpoints || evoSpawnCharData.hitpoints || baseHP; - const baseEvoDmg = evoCharData.damage || evoProjData.damage || evoAreaData.damage || evoBuffData.damagePerSecond || evoSpawnProjData.damage || evoSpawnCharData.damage || baseDamage; + const baseEvoDmg = evoCharData.damage || evoProjData.damage || evoAreaData.damage || evoBuffData.damagePerSecond || + evoSpawnProjData.damage || evoSpawnCharData.damage || baseDamage; const evoHPStats = calculateStats(baseEvoHP, multipliers); const evoDmgStats = calculateStats(baseEvoDmg, multipliers); @@ -230,13 +328,23 @@ async function main() { level15: evoDmgStats.level15 ?? (card.statsEvo.damage ? card.statsEvo.damage.level15 : null), level16: evoDmgStats.level16 ?? (card.statsEvo.damage ? card.statsEvo.damage.level16 : null) }; + + // Evolution specific cycles + if (apiItem.evolvedSpellsData.cycles) { + card.statsEvo.cycles = apiItem.evolvedSpellsData.cycles; + } } // Hero check - if (apiItem.heroData) { + /* if (apiItem.heroData) { card.hero = true; - card.statsHero.prestigeCost = apiItem.heroData.prestigeCount ?? null; - } + const prestige = apiItem.heroData.prestigeCount; + if (prestige !== undefined) { + card.statsHero.prestigeCost = prestige; + } + } else if (apiItem.rarity === 'Champion') { + card.hero = true; + } */ // Fallback: Extrapolate missing Level 16 from Level 11 const statFields = ['hitpoints', 'damage', 'fatalDamage', 'chargeDamage', 'towerDamage']; @@ -270,13 +378,17 @@ async function main() { }); } }); + // Ensure radius is numeric if it exists + if (card.radius === null && 'radius' in card) { + // keep it null + } }); // Write back to file console.log('Writing to cards.json...'); fs.writeFileSync(CARDS_FILE, JSON.stringify(cardsJson, null, 4), 'utf8'); - console.log(`Update complete!`); + console.log('Update complete!'); console.log(`Updated: ${updatedCount} cards`); console.log(`Added: ${addedCount} new cards`); From 15be03bffe382c6655a66befbb8690ef43187519 Mon Sep 17 00:00:00 2001 From: Gamaliel Date: Tue, 27 Jan 2026 13:30:29 -0400 Subject: [PATCH 08/10] fix(data): update card target definitions in cards.json Update the target types for various cards to ensure data consistency and accuracy. This includes reordering target arrays, adding missing target capabilities for specific units, and removing incorrect target types where applicable. Key changes: - Reordered "ground" and "air" tags across multiple card entries for consistency. - Added missing "air" and "ground" targets to specific cards (e.g., IDs 26000048, 26000060). - Removed "buildings" target from units where it was incorrectly assigned (e.g., IDs 27000002, 27000008). --- cards.json | 94 +++++++++++++++++++++++++++--------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/cards.json b/cards.json index 1ceb956..61bc02f 100644 --- a/cards.json +++ b/cards.json @@ -331,8 +331,8 @@ "id": 26000005, "elixirCost": 3, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 3, "duration": null, @@ -1966,8 +1966,8 @@ "id": 26000030, "elixirCost": 1, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, @@ -2032,8 +2032,8 @@ "id": 26000031, "elixirCost": 1, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, @@ -2098,7 +2098,6 @@ "id": 26000032, "elixirCost": 3, "targets": [ - "buildings", "ground" ], "units": 1, @@ -2687,8 +2686,8 @@ "id": 26000041, "elixirCost": 3, "targets": [ - "air", - "ground" + "ground", + "air" ], "units": 6, "duration": null, @@ -3146,7 +3145,8 @@ "id": 26000048, "elixirCost": 4, "targets": [ - "ground" + "ground", + "air" ], "units": 1, "duration": null, @@ -3343,8 +3343,8 @@ "elixirCost": 5, "targets": [ "buildings", - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, @@ -3475,8 +3475,8 @@ "id": 26000053, "elixirCost": 5, "targets": [ - "air", - "ground" + "ground", + "air" ], "units": 3, "duration": null, @@ -3932,7 +3932,9 @@ "id": 26000060, "elixirCost": 6, "targets": [ - "buildings" + "buildings", + "air", + "ground" ], "units": 1, "duration": null, @@ -4848,8 +4850,8 @@ "id": 26000084, "elixirCost": 1, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, @@ -5307,8 +5309,8 @@ "elixirCost": 5, "targets": [ "buildings", - "ground", - "air" + "air", + "ground" ], "units": 2, "duration": null, @@ -5699,8 +5701,7 @@ "id": 27000002, "elixirCost": 4, "targets": [ - "ground", - "buildings" + "ground" ], "units": 1, "duration": 30.0, @@ -5765,8 +5766,8 @@ "id": 27000003, "elixirCost": 5, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": 30.0, @@ -5961,8 +5962,8 @@ "id": 27000006, "elixirCost": 4, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": 30.0, @@ -6090,8 +6091,7 @@ "id": 27000008, "elixirCost": 6, "targets": [ - "ground", - "buildings" + "ground" ], "units": 1, "duration": 30.0, @@ -6221,8 +6221,8 @@ "id": 27000010, "elixirCost": 4, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, @@ -6418,8 +6418,8 @@ "id": 28000000, "elixirCost": 4, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 0, "duration": null, @@ -6484,8 +6484,8 @@ "id": 28000001, "elixirCost": 3, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 0, "duration": null, @@ -6616,8 +6616,8 @@ "id": 28000003, "elixirCost": 6, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 0, "duration": null, @@ -7468,8 +7468,8 @@ "id": 28000016, "elixirCost": 1, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": 1.0, @@ -7534,8 +7534,8 @@ "id": 28000017, "elixirCost": 2, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 0, "duration": null, @@ -7932,8 +7932,8 @@ "id": 159000000, "elixirCost": 0, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, @@ -7998,8 +7998,8 @@ "id": 159000001, "elixirCost": 0, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, @@ -8064,8 +8064,8 @@ "id": 159000002, "elixirCost": 0, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, @@ -8130,8 +8130,8 @@ "id": 159000004, "elixirCost": 0, "targets": [ - "ground", - "air" + "air", + "ground" ], "units": 1, "duration": null, From 499f0edc404dd0e3b0d0c861bc421e5621d7a7ed Mon Sep 17 00:00:00 2001 From: Gamaliel Date: Tue, 27 Jan 2026 13:31:46 -0400 Subject: [PATCH 09/10] build(update-cards): refactor update-cards script for improved data mapping Refactored the `scripts/update-cards.js` script to improve the logic for fetching and processing card data from the API. Key changes include: - Added `SPEED_MAP` and `TARGETS_MAP` for consistent data normalization. - Reorganized the script into dedicated sections for constants, helpers, and processing logic. - Implemented robust stat calculation and merging functions to handle level scaling (11, 15, 16) and multipliers. - Enhanced the mapping of character, projectile, area, and spawn data to provide more accurate card attributes like radius, tower damage, and generation stats. - Cleaned up the `CARD_SKELETON` structure for better readability. --- scripts/update-cards.js | 550 +++++++++++++++++----------------------- 1 file changed, 227 insertions(+), 323 deletions(-) diff --git a/scripts/update-cards.js b/scripts/update-cards.js index 790894c..2135605 100644 --- a/scripts/update-cards.js +++ b/scripts/update-cards.js @@ -1,13 +1,11 @@ -/** - * @fileoverview Script to update cards.json with data from galacticapricot API. - * - * Usage: node scripts/update-cards.js - */ - const fs = require('fs'); const path = require('path'); const https = require('https'); +// ───────────────────────────────────────────────────────────────────────────── +// CONSTANTS +// ───────────────────────────────────────────────────────────────────────────── + const API_URL = 'https://humble.galacticapricot.dev/gamedata-v5.json'; const CARDS_FILE = path.join(__dirname, '..', 'cards.json'); @@ -16,18 +14,22 @@ const MULTIPLIERS = { tower: { level11: 2.18, level15: 3.16, level16: 3.46 } }; +const SPEED_MAP = { + 'TID_SPEED_0': 'slow', 'TID_SPEED_1': 'slow', 'TID_SPEED_2': 'slow', + 'TID_SPEED_3': 'medium', 'TID_SPEED_4': 'fast', 'TID_SPEED_5': 'very-fast' +}; + +const TARGETS_MAP = { + 'TID_TARGETS_GROUND': ['ground'], + 'TID_TARGETS_AIR_AND_GROUND': ['air', 'ground'], + 'TID_TARGETS_BUILDINGS': ['buildings'], + 'TID_TARGETS_NONE': [] +}; + const CARD_SKELETON = { - name: null, - id: null, - elixirCost: null, - targets: [], - units: 1, - duration: null, - evolution: false, - hero: false, - typeAttack: null, - projectile: false, - suicide: false, + name: null, id: null, elixirCost: null, targets: [], units: 1, + duration: null, evolution: false, hero: false, typeAttack: null, + projectile: false, suicide: false, fatalDamage: { level11: null, level15: null, level16: null }, chargeDamage: { level11: null, level15: null, level16: null }, towerDamage: { level11: null, level15: null, level16: null }, @@ -38,78 +40,222 @@ const CARD_SKELETON = { damage: { level11: null, level15: null, level16: null }, hitpoints: { level11: null, level15: null, level16: null } }, - statsHero: { - prestigeCost: null - }, - hitspeed: null, - radius: null, - generationSpeed: null, - generationUnits: null, - speed: null, - range: null, - territory: null, - rarity: null, - type: null + statsHero: { prestigeCost: null }, + hitspeed: null, radius: null, generationSpeed: null, generationUnits: null, + speed: null, range: null, territory: null, rarity: null, type: null }; -const SPEED_MAP = { - 'TID_SPEED_0': 'slow', - 'TID_SPEED_1': 'slow', - 'TID_SPEED_2': 'slow', - 'TID_SPEED_3': 'medium', - 'TID_SPEED_4': 'fast', - 'TID_SPEED_5': 'very-fast' +// ───────────────────────────────────────────────────────────────────────────── +// HELPERS +// ───────────────────────────────────────────────────────────────────────────── + +const fetchData = (url) => new Promise((resolve, reject) => { + https.get(url, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + try { resolve(JSON.parse(data)); } + catch (e) { reject(new Error('Failed to parse API response: ' + e.message)); } + }); + }).on('error', reject); +}); + +const calcStats = (base, m) => base + ? { level11: Math.round(base * m.level11), level15: Math.round(base * m.level15), level16: Math.round(base * m.level16) } + : { level11: null, level15: null, level16: null }; + +const mergeStats = (computed, existing) => ({ + level11: computed.level11 ?? existing?.level11 ?? null, + level15: computed.level15 ?? existing?.level15 ?? null, + level16: computed.level16 ?? existing?.level16 ?? null +}); + +const getTargets = (tid) => TARGETS_MAP[tid] || []; + +const extractCharData = (item) => { + if (item.summonCharacterData) return item.summonCharacterData; + if (item.statCharacterData) return item.statCharacterData; + const areaSpawn = item.areaEffectObjectData?.onStartingActionData?.spawnDataData; + return areaSpawn || {}; }; -const TARGETS_MAP = { - 'TID_TARGETS_GROUND': ['ground'], - 'TID_TARGETS_AIR_AND_GROUND': ['air', 'ground'], - 'TID_TARGETS_BUILDINGS': ['buildings'], - 'TID_TARGETS_NONE': [] -}; +const cloneDeep = (obj) => JSON.parse(JSON.stringify(obj)); -/** - * Fetch JSON data from URL - */ -function fetchData(url) { - return new Promise((resolve, reject) => { - https.get(url, (res) => { - let data = ''; - res.on('data', (chunk) => data += chunk); - res.on('end', () => { - try { - resolve(JSON.parse(data)); - } catch (e) { - reject(new Error('Failed to parse API response: ' + e.message)); - } - }); - }).on('error', (err) => reject(err)); +// ───────────────────────────────────────────────────────────────────────────── +// CARD PROCESSING +// ───────────────────────────────────────────────────────────────────────────── + +function collectCharacters(apiItem) { + const chars = []; + if (apiItem.summonCharacterData) chars.push({ data: apiItem.summonCharacterData, count: apiItem.summonNumber || 1 }); + if (apiItem.summonCharacterSecondData) chars.push({ data: apiItem.summonCharacterSecondData, count: apiItem.summonCharacterSecondCount || 1 }); + if (apiItem.summonCharacterThirdData) chars.push({ data: apiItem.summonCharacterThirdData, count: apiItem.summonCharacterThirdCount || 1 }); + return chars; +} + +function aggregateCharacterStats(characters) { + let maxHP = 0, primaryChar = {}, totalDamage = 0, hasProjectile = false; + + characters.forEach(({ data }) => { + const hp = data.hitpoints || 0; + const proj = data.projectileData || {}; + const dmg = data.damage || proj.damage || 0; + + if (hp > maxHP) { maxHP = hp; primaryChar = data; } + if (dmg > totalDamage) totalDamage = dmg; + if (Object.keys(proj).length > 0) hasProjectile = true; }); + + return { maxHP, primaryChar, totalDamage, hasProjectile }; +} + +function extractAllTargets(apiItem, charData) { + const sources = [ + apiItem.tidTarget, + apiItem.projectileData?.tidTarget, + charData.tidTarget, + charData.spawnCharacterData?.tidTarget + ]; + + ['summonCharacterData', 'summonCharacterSecondData', 'summonCharacterThirdData'].forEach(key => { + const data = apiItem[key]; + if (data) { + sources.push(data.tidTarget, data.spawnCharacterData?.tidTarget); + } + }); + + return [...new Set(sources.flatMap(getTargets))]; +} + +function processCard(card, apiItem, multipliers) { + const charData = extractCharData(apiItem); + const areaData = apiItem.areaEffectObjectData || {}; + const projData = apiItem.projectileData || charData.projectileData || areaData.projectileData || {}; + const buffData = areaData.buffData || {}; + const spawnProjData = projData.spawnProjectileData || areaData.projectileData || {}; + const spawnCharData = projData.spawnCharacterData || areaData.spawnCharacterData || {}; + + card.elixirCost = apiItem.manaCost ?? card.elixirCost; + card.rarity = (apiItem.rarity || card.rarity || '').toLowerCase(); + + const allTargets = extractAllTargets(apiItem, charData); + if (allTargets.length > 0) card.targets = allTargets; + + card.units = (apiItem.summonNumber || 0) + (apiItem.summonCharacterSecondCount || 0) + (apiItem.summonCharacterThirdCount || 0) || card.units; + + const characters = collectCharacters(apiItem); + let baseHP, baseDamage; + + if (characters.length > 0) { + const { maxHP, primaryChar, totalDamage, hasProjectile } = aggregateCharacterStats(characters); + card.hitspeed = primaryChar.hitSpeed ? primaryChar.hitSpeed / 1000 : card.hitspeed; + card.range = primaryChar.range ? primaryChar.range / 1000 : card.range; + card.speed = SPEED_MAP[primaryChar.tidSpeed] || card.speed; + card.projectile = hasProjectile || card.projectile; + baseHP = maxHP || charData.hitpoints || spawnCharData.hitpoints; + baseDamage = totalDamage || charData.damage || projData.damage || areaData.damage || buffData.damagePerSecond || spawnProjData.damage || spawnCharData.damage; + } else { + card.hitspeed = charData.hitSpeed ? charData.hitSpeed / 1000 : card.hitspeed; + card.range = charData.range ? charData.range / 1000 : card.range; + card.speed = SPEED_MAP[charData.tidSpeed] || card.speed; + baseHP = charData.hitpoints || spawnCharData.hitpoints; + baseDamage = charData.damage || projData.damage || areaData.damage || buffData.damagePerSecond || spawnProjData.damage || spawnCharData.damage; + } + + card.generationSpeed = charData.spawnPauseTime ? charData.spawnPauseTime / 1000 : card.generationSpeed; + card.generationUnits = charData.spawnNumber > 1 ? charData.spawnNumber : card.generationUnits; + + const rawRadius = apiItem.radius ?? areaData.radius ?? charData.areaDamageRadius ?? projData.radius ?? projData.customFirstProjectileData?.radius; + if (rawRadius != null) { + card.radius = rawRadius / 1000; + card.typeAttack = 'splash'; + } else { + card.typeAttack = card.typeAttack || 'unique'; + } + + card.projectile = Object.keys(projData).length > 0 || card.projectile; + + const rawDuration = apiItem.lifeTime ?? areaData.lifeDuration; + if (rawDuration != null) card.duration = rawDuration / 1000; + + const baseFatal = charData.deathDamage || charData.deathSpawnCharacterData?.deathDamage; + const baseCharge = charData.damageSpecial; + + const towerDamagePercent = apiItem.crownTowerDamagePercent ?? charData.crownTowerDamagePercent ?? projData.crownTowerDamagePercent ?? + areaData.crownTowerDamagePercent ?? buffData.crownTowerDamagePercent ?? spawnProjData.crownTowerDamagePercent ?? spawnCharData.crownTowerDamagePercent; + const baseTowerDamage = towerDamagePercent !== undefined && baseDamage ? baseDamage * (100 + towerDamagePercent) / 100 : null; + + card.hitpoints = mergeStats(calcStats(baseHP, multipliers), card.hitpoints); + card.damage = mergeStats(calcStats(baseDamage, multipliers), card.damage); + card.fatalDamage = mergeStats(calcStats(baseFatal, multipliers), card.fatalDamage); + card.chargeDamage = mergeStats(calcStats(baseCharge, multipliers), card.chargeDamage); + card.towerDamage = mergeStats(calcStats(baseTowerDamage, multipliers), card.towerDamage); + + processEvolution(card, apiItem, multipliers, baseHP, baseDamage); + extrapolateLevel16(card, multipliers); } -/** - * Calculate stat for level 11 and 15 - */ -function calculateStats(base, multipliers) { - if (base === null || base === undefined || base === 0) return { level11: null, level15: null, level16: null }; - return { - level11: Math.round(base * multipliers.level11), - level15: Math.round(base * multipliers.level15), - level16: Math.round(base * multipliers.level16) +function processEvolution(card, apiItem, multipliers, baseHP, baseDamage) { + if (!apiItem.evolvedSpellsData) return; + + card.evolution = true; + const evoData = apiItem.evolvedSpellsData; + + let evoCharData = evoData.summonCharacterData || {}; + if (Object.keys(evoCharData).length === 0) { + evoCharData = evoData.areaEffectObjectData?.onStartingActionData?.spawnDataData || {}; + } + + const evoProjData = evoData.projectileData || evoCharData.projectileData || {}; + const evoAreaData = evoData.areaEffectObjectData || {}; + const evoBuffData = evoAreaData.buffData || {}; + const evoSpawnProjData = evoProjData.spawnProjectileData || evoAreaData.projectileData || {}; + const evoSpawnCharData = evoProjData.spawnCharacterData || evoAreaData.spawnCharacterData || {}; + + const baseEvoHP = evoCharData.hitpoints || evoSpawnCharData.hitpoints || baseHP; + const baseEvoDmg = evoCharData.damage || evoProjData.damage || evoAreaData.damage || + evoBuffData.damagePerSecond || evoSpawnProjData.damage || evoSpawnCharData.damage || baseDamage; + + card.statsEvo.hitpoints = mergeStats(calcStats(baseEvoHP, multipliers), card.statsEvo.hitpoints); + card.statsEvo.damage = mergeStats(calcStats(baseEvoDmg, multipliers), card.statsEvo.damage); + card.statsEvo.cycles = evoData.cycles ?? card.statsEvo.cycles; +} + +function extrapolateLevel16(card, multipliers) { + const extrapolate = (stats) => { + if (stats && stats.level16 == null && stats.level11 != null) { + stats.level16 = Math.round((stats.level11 / multipliers.level11) * multipliers.level16); + } }; + + ['hitpoints', 'damage', 'fatalDamage', 'chargeDamage', 'towerDamage'].forEach(f => extrapolate(card[f])); + if (card.statsEvo) { + ['hitpoints', 'damage'].forEach(f => extrapolate(card.statsEvo[f])); + } +} + +function ensureCardSkeleton(card) { + Object.keys(CARD_SKELETON).forEach(key => { + if (!(key in card)) { + card[key] = cloneDeep(CARD_SKELETON[key]); + } else if (CARD_SKELETON[key] && typeof CARD_SKELETON[key] === 'object' && !Array.isArray(CARD_SKELETON[key])) { + Object.keys(CARD_SKELETON[key]).forEach(nested => { + if (!(nested in card[key])) card[key][nested] = CARD_SKELETON[key][nested]; + }); + } + }); } -/** - * Main update logic - */ +// ───────────────────────────────────────────────────────────────────────────── +// MAIN +// ───────────────────────────────────────────────────────────────────────────── + async function main() { try { console.log('Fetching game data...'); const apiData = await fetchData(API_URL); - if (!apiData || !apiData.items || !apiData.items.spells) { - throw new Error('Invalid API data structure'); - } + if (!apiData?.items?.spells) throw new Error('Invalid API data structure'); console.log('Reading cards.json...'); const cardsJson = JSON.parse(fs.readFileSync(CARDS_FILE, 'utf8')); @@ -118,14 +264,10 @@ async function main() { cardsJson.cards.forEach(c => existingCardsMap.set(c.id, { card: c, list: cardsJson.cards })); cardsJson.towerCards.forEach(c => existingCardsMap.set(c.id, { card: c, list: cardsJson.towerCards })); - let updatedCount = 0; - let addedCount = 0; + let updatedCount = 0, addedCount = 0; apiData.items.spells.forEach(apiItem => { - // Skip Super cards and event cards - if (apiItem.name.startsWith('Super') || apiItem.notVisible) { - return; - } + if (apiItem.name.startsWith('Super') || apiItem.notVisible) return; const isTower = apiItem.tidType === 'TID_TYPE_TOWER_TROOP' || apiItem.source === 'support_cards'; const multipliers = isTower ? MULTIPLIERS.tower : MULTIPLIERS.standard; @@ -134,264 +276,26 @@ async function main() { let card; if (!entry) { - // New card skeleton - card = JSON.parse(JSON.stringify(CARD_SKELETON)); + card = cloneDeep(CARD_SKELETON); card.id = apiItem.id; card.name = apiItem.englishName || apiItem.name; - card.type = isTower ? 'tower' : 'troop'; // Default, adjusted below - - if (isTower) { - cardsJson.towerCards.push(card); - } else { - cardsJson.cards.push(card); - } + card.type = isTower ? 'tower' : 'troop'; + (isTower ? cardsJson.towerCards : cardsJson.cards).push(card); addedCount++; } else { card = entry.card; updatedCount++; } - // Update basic info if missing or new - card.elixirCost = apiItem.manaCost ?? card.elixirCost; - - const extractTargets = (tid) => TARGETS_MAP[tid] || []; - const allTargets = [ - ...extractTargets(apiItem.tidTarget), - ...extractTargets(apiItem.projectileData?.tidTarget), - ...extractTargets(apiItem.summonCharacterData?.tidTarget), - ...extractTargets(apiItem.summonCharacterData?.spawnCharacterData?.tidTarget), - ...extractTargets(apiItem.summonCharacterSecondData?.tidTarget), - ...extractTargets(apiItem.summonCharacterSecondData?.spawnCharacterData?.tidTarget), - ...extractTargets(apiItem.summonCharacterThirdData?.tidTarget), - ...extractTargets(apiItem.summonCharacterThirdData?.spawnCharacterData?.tidTarget) - ]; - card.targets = [...(new Set(allTargets).length > 0 ? new Set(allTargets) : card.targets)]; - card.rarity = (apiItem.rarity || card.rarity || '').toLowerCase(); - card.units = (apiItem.summonNumber || 0) + (apiItem.summonCharacterSecondCount || 0) + (apiItem.summonCharacterThirdCount || 0) || card.units; - - // Extract base stats - // Handle multiple characters - const characters = []; - if (apiItem.summonCharacterData) characters.push({ data: apiItem.summonCharacterData, count: apiItem.summonNumber || 1 }); - if (apiItem.summonCharacterSecondData) characters.push({ data: apiItem.summonCharacterSecondData, count: apiItem.summonCharacterSecondCount || 1 }); - if (apiItem.summonCharacterThirdData) characters.push({ data: apiItem.summonCharacterThirdData, count: apiItem.summonCharacterThirdCount || 1 }); - - // fallback charData for single-unit logic or backward compatibility - let charData = apiItem.summonCharacterData || apiItem.statCharacterData || {}; - - // Handle cases where character data is inside area effect - if (Object.keys(charData).length === 0 && apiItem.areaEffectObjectData && apiItem.areaEffectObjectData.onStartingActionData && apiItem.areaEffectObjectData.onStartingActionData.spawnDataData) { - charData = apiItem.areaEffectObjectData.onStartingActionData.spawnDataData; - } - - const areaData = apiItem.areaEffectObjectData || {}; - const projData = apiItem.projectileData || charData.projectileData || areaData.projectileData || {}; - const buffData = areaData.buffData || {}; - const spawnProjData = projData.spawnProjectileData || areaData.projectileData || {}; - const spawnCharData = projData.spawnCharacterData || areaData.spawnCharacterData || {}; - - // Aggregation logic for multi-units - let maxHP = 0; - let primaryChar = charData; - let totalDamage = 0; - - if (characters.length > 0) { - characters.forEach(c => { - const d = c.data; - const hp = d.hitpoints || 0; - const p = d.projectileData || {}; - const unitDamage = d.damage || p.damage || 0; - - if (hp > maxHP) { - maxHP = hp; - primaryChar = d; - } - - if (unitDamage > totalDamage) { - totalDamage = unitDamage; - } - - if (Object.keys(p).length > 0) { - card.projectile = true; - } - }); - - card.hitspeed = (primaryChar.hitSpeed ? primaryChar.hitSpeed / 1000 : card.hitspeed); - card.range = (primaryChar.range ? primaryChar.range / 1000 : card.range); - card.speed = SPEED_MAP[primaryChar.tidSpeed] || card.speed; - } else { - card.hitspeed = (charData.hitSpeed ? charData.hitSpeed / 1000 : card.hitspeed); - card.range = (charData.range ? charData.range / 1000 : card.range); - card.speed = SPEED_MAP[charData.tidSpeed] || card.speed; - } - - card.generationSpeed = (charData.spawnPauseTime ? charData.spawnPauseTime / 1000 : card.generationSpeed); - card.generationUnits = ((charData.spawnNumber && charData.spawnNumber > 1) ? charData.spawnNumber : card.generationUnits); - - // Radius and Type Attack - const rawRadius = apiItem.radius ?? areaData.radius ?? charData.areaDamageRadius ?? projData.radius ?? - (projData.customFirstProjectileData ? projData.customFirstProjectileData.radius : null); - if (rawRadius !== null && rawRadius !== undefined) { - card.radius = rawRadius / 1000; - card.typeAttack = 'splash'; - } else { - card.typeAttack = card.typeAttack || 'unique'; - } - - // Projectile - card.projectile = (Object.keys(projData).length > 0 || card.projectile); - - // Life duration / Duration - const rawDuration = apiItem.lifeTime ?? areaData.lifeDuration ?? null; - if (rawDuration !== null) { - card.duration = rawDuration / 1000; - } - - const baseHP = maxHP || charData.hitpoints || spawnCharData.hitpoints || null; - const baseDamage = totalDamage || charData.damage || projData.damage || areaData.damage || buffData.damagePerSecond || - spawnProjData.damage || spawnCharData.damage || null; - const baseFatal = charData.deathDamage || (charData.deathSpawnCharacterData ? charData.deathSpawnCharacterData.deathDamage : null) || null; - const baseCharge = charData.damageSpecial || null; - - // Tower damage calculation for spells/troops - let baseTowerDamage = null; - const towerDamagePercent = apiItem.crownTowerDamagePercent ?? charData.crownTowerDamagePercent ?? projData.crownTowerDamagePercent ?? - areaData.crownTowerDamagePercent ?? buffData.crownTowerDamagePercent ?? spawnProjData.crownTowerDamagePercent ?? spawnCharData.crownTowerDamagePercent; - if (towerDamagePercent !== undefined && baseDamage) { - baseTowerDamage = baseDamage * (100 + towerDamagePercent) / 100; - } - - // Update stats - const hpStats = calculateStats(baseHP, multipliers); - const dmgStats = calculateStats(baseDamage, multipliers); - const fatalStats = calculateStats(baseFatal, multipliers); - const chargeStats = calculateStats(baseCharge, multipliers); - const towerDmgStats = calculateStats(baseTowerDamage, multipliers); - - card.hitpoints = { - level11: hpStats.level11 ?? card.hitpoints.level11 ?? null, - level15: hpStats.level15 ?? card.hitpoints.level15 ?? null, - level16: hpStats.level16 ?? card.hitpoints.level16 ?? null - }; - card.damage = { - level11: dmgStats.level11 ?? card.damage.level11 ?? null, - level15: dmgStats.level15 ?? card.damage.level15 ?? null, - level16: dmgStats.level16 ?? card.damage.level16 ?? null - }; - card.fatalDamage = { - level11: fatalStats.level11 ?? card.fatalDamage.level11 ?? null, - level15: fatalStats.level15 ?? card.fatalDamage.level15 ?? null, - level16: fatalStats.level16 ?? card.fatalDamage.level16 ?? null - }; - card.chargeDamage = { - level11: chargeStats.level11 ?? card.chargeDamage.level11 ?? null, - level15: chargeStats.level15 ?? card.chargeDamage.level15 ?? null, - level16: chargeStats.level16 ?? card.chargeDamage.level16 ?? null - }; - card.towerDamage = { - level11: towerDmgStats.level11 ?? card.towerDamage.level11 ?? null, - level15: towerDmgStats.level15 ?? card.towerDamage.level15 ?? null, - level16: towerDmgStats.level16 ?? card.towerDamage.level16 ?? null - }; - - // Evolution check - if (apiItem.evolvedSpellsData) { - card.evolution = true; - const evoData = apiItem.evolvedSpellsData; - let evoCharData = evoData.summonCharacterData || {}; - - if (Object.keys(evoCharData).length === 0 && evoData.areaEffectObjectData && evoData.areaEffectObjectData.onStartingActionData && - evoData.areaEffectObjectData.onStartingActionData.spawnDataData) { - evoCharData = evoData.areaEffectObjectData.onStartingActionData.spawnDataData; - } - - const evoProjData = evoData.projectileData || (evoCharData.projectileData) || {}; - const evoAreaData = evoData.areaEffectObjectData || {}; - const evoBuffData = evoAreaData.buffData || {}; - const evoSpawnProjData = evoProjData.spawnProjectileData || evoAreaData.projectileData || {}; - const evoSpawnCharData = evoProjData.spawnCharacterData || evoAreaData.spawnCharacterData || {}; - - const baseEvoHP = evoCharData.hitpoints || evoSpawnCharData.hitpoints || baseHP; - const baseEvoDmg = evoCharData.damage || evoProjData.damage || evoAreaData.damage || evoBuffData.damagePerSecond || - evoSpawnProjData.damage || evoSpawnCharData.damage || baseDamage; - - const evoHPStats = calculateStats(baseEvoHP, multipliers); - const evoDmgStats = calculateStats(baseEvoDmg, multipliers); - - card.statsEvo.hitpoints = { - level11: evoHPStats.level11 ?? (card.statsEvo.hitpoints ? card.statsEvo.hitpoints.level11 : null), - level15: evoHPStats.level15 ?? (card.statsEvo.hitpoints ? card.statsEvo.hitpoints.level15 : null), - level16: evoHPStats.level16 ?? (card.statsEvo.hitpoints ? card.statsEvo.hitpoints.level16 : null) - }; - card.statsEvo.damage = { - level11: evoDmgStats.level11 ?? (card.statsEvo.damage ? card.statsEvo.damage.level11 : null), - level15: evoDmgStats.level15 ?? (card.statsEvo.damage ? card.statsEvo.damage.level15 : null), - level16: evoDmgStats.level16 ?? (card.statsEvo.damage ? card.statsEvo.damage.level16 : null) - }; - - // Evolution specific cycles - if (apiItem.evolvedSpellsData.cycles) { - card.statsEvo.cycles = apiItem.evolvedSpellsData.cycles; - } - } - - // Hero check - /* if (apiItem.heroData) { - card.hero = true; - const prestige = apiItem.heroData.prestigeCount; - if (prestige !== undefined) { - card.statsHero.prestigeCost = prestige; - } - } else if (apiItem.rarity === 'Champion') { - card.hero = true; - } */ - - // Fallback: Extrapolate missing Level 16 from Level 11 - const statFields = ['hitpoints', 'damage', 'fatalDamage', 'chargeDamage', 'towerDamage']; - statFields.forEach(field => { - if (card[field] && (card[field].level16 === null || card[field].level16 === undefined) && card[field].level11 !== null) { - card[field].level16 = Math.round((card[field].level11 / multipliers.level11) * multipliers.level16); - } - }); - if (card.statsEvo) { - ['hitpoints', 'damage'].forEach(field => { - if (card.statsEvo[field] && (card.statsEvo[field].level16 === null || card.statsEvo[field].level16 === undefined) && card.statsEvo[field].level11 !== null) { - card.statsEvo[field].level16 = Math.round((card.statsEvo[field].level11 / multipliers.level11) * multipliers.level16); - } - }); - } - + processCard(card, apiItem, multipliers); }); - // Final pass: Ensure all cards follow the skeleton (including level16 nested keys) - const allResultCards = [...cardsJson.cards, ...cardsJson.towerCards]; - allResultCards.forEach(card => { - Object.keys(CARD_SKELETON).forEach(key => { - if (!(key in card)) { - card[key] = JSON.parse(JSON.stringify(CARD_SKELETON[key])); - } else if (CARD_SKELETON[key] !== null && typeof CARD_SKELETON[key] === 'object' && !Array.isArray(CARD_SKELETON[key])) { - // Deep check for nested keys (like level16) - Object.keys(CARD_SKELETON[key]).forEach(nestedKey => { - if (!(nestedKey in card[key])) { - card[key][nestedKey] = CARD_SKELETON[key][nestedKey]; - } - }); - } - }); - // Ensure radius is numeric if it exists - if (card.radius === null && 'radius' in card) { - // keep it null - } - }); + [...cardsJson.cards, ...cardsJson.towerCards].forEach(ensureCardSkeleton); - // Write back to file console.log('Writing to cards.json...'); fs.writeFileSync(CARDS_FILE, JSON.stringify(cardsJson, null, 4), 'utf8'); - console.log('Update complete!'); - console.log(`Updated: ${updatedCount} cards`); - console.log(`Added: ${addedCount} new cards`); - + console.log(`Update complete! Updated: ${updatedCount}, Added: ${addedCount}`); } catch (error) { console.error('Error:', error.message); process.exit(1); From 65278afafae69c5efa33d663af866663eae43be9 Mon Sep 17 00:00:00 2001 From: Gamaliel Date: Tue, 27 Jan 2026 13:34:38 -0400 Subject: [PATCH 10/10] chore(eslint): initialize eslint with flat configuration - Install eslint, @eslint/js, and globals as development dependencies - Create eslint.config.js using the new flat configuration format - Configure recommended rules along with custom overrides for indentation, quotes, and semi-colons - Set up execution environments for Node.js and Jest globals --- eslint.config.js | 25 ++ package-lock.json | 818 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 9 +- 3 files changed, 850 insertions(+), 2 deletions(-) create mode 100644 eslint.config.js diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..b6c38bd --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,25 @@ +import js from '@eslint/js'; +import globals from 'globals'; + +export default [ + js.configs.recommended, + { + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + globals: { + ...globals.node, + ...globals.jest, + }, + }, + rules: { + 'no-unused-vars': 'warn', + 'no-console': 'off', + 'indent': ['error', 4], + 'quotes': ['error', 'single'], + 'semi': ['error', 'always'], + 'no-undef': 'error', + 'no-use-before-define': ['error', { functions: false, classes: true, variables: true }], + }, + }, +]; diff --git a/package-lock.json b/package-lock.json index b9f5426..5fd0870 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,15 @@ "version": "1.8.0", "license": "Apache-2.0", "devDependencies": { + "@eslint/js": "^9.39.2", "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^11.0.2", "@types/ajv": "^0.0.5", "@types/jest": "^30.0.0", "ajv": "^8.17.1", + "eslint": "^9.39.2", + "globals": "^17.1.0", "jest": "^30.0.4", "jest-environment-jsdom": "^30.0.4", "semantic-release": "^24.2.4" @@ -697,6 +700,307 @@ "tslib": "^2.4.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2101,6 +2405,13 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -2151,6 +2462,13 @@ "parse5": "^7.0.0" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.5.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", @@ -2475,6 +2793,29 @@ "win32" ] }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -3519,6 +3860,13 @@ "node": ">=4.0.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -3840,6 +4188,224 @@ "node": ">=8" } }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -3854,6 +4420,52 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -3937,6 +4549,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -3980,6 +4599,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4037,6 +4669,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -4207,6 +4860,32 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.1.0.tgz", + "integrity": "sha512-8HoIcWI5fCvG5NADj4bDav+er9B9JMj2vyL2pI8D0eismKyUvPLTSs+Ln3wqhwcp306i73iyVnEKx3F6T47TGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -4360,6 +5039,16 @@ "node": ">=0.10.0" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -4515,6 +5204,16 @@ "dev": true, "license": "MIT" }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -4535,6 +5234,19 @@ "node": ">=6" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5447,6 +6159,13 @@ "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -5468,6 +6187,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -5494,6 +6220,16 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -5504,6 +6240,20 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -5606,6 +6356,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.uniqby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", @@ -8718,6 +9475,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-each-series": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", @@ -9121,6 +9896,16 @@ "node": ">=8" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-format": { "version": "30.0.5", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", @@ -10574,6 +11359,19 @@ "license": "0BSD", "optional": true }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -10740,6 +11538,16 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/url-join": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", @@ -10869,6 +11677,16 @@ "node": ">= 8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", diff --git a/package.json b/package.json index 92f9e4a..4cecc88 100644 --- a/package.json +++ b/package.json @@ -20,18 +20,23 @@ "cards.json" ], "devDependencies": { + "@eslint/js": "^9.39.2", "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^11.0.2", "@types/ajv": "^0.0.5", "@types/jest": "^30.0.0", "ajv": "^8.17.1", + "eslint": "^9.39.2", + "globals": "^17.1.0", "jest": "^30.0.4", "jest-environment-jsdom": "^30.0.4", "semantic-release": "^24.2.4" }, "scripts": { - "test": "jest /tests/cards.test.js", + "test": "jest tests/cards.test.js", + "lint": "eslint .", + "lint:fix": "eslint . --fix", "buid:update-cards": "node scripts/update-cards.js && node scripts/convert-to-floats.js" } -} +} \ No newline at end of file