import { createServer } from 'vite'; const validRanks = new Set(['common', 'treasure']); const server = await createServer({ logLevel: 'error', server: { middlewareMode: true }, appType: 'custom' }); try { const { battleScenarios } = await server.ssrLoadModule('/src/game/data/battles.ts'); const { campaignRecruitUnits } = await server.ssrLoadModule('/src/game/data/scenario.ts'); const { itemCatalog, equipmentSlots, equipmentExpToNext } = await server.ssrLoadModule('/src/game/data/battleItems.ts'); const errors = []; const itemEntries = Object.entries(itemCatalog); const itemKeys = new Set(itemEntries.map(([itemId]) => itemId)); const slotKeys = new Set(equipmentSlots); validateItemCatalog(errors, itemEntries, slotKeys); validateUnitEquipment(errors, collectBattleUnits(battleScenarios), itemCatalog, itemKeys, equipmentSlots, equipmentExpToNext); validateUnitEquipment( errors, (campaignRecruitUnits ?? []).map((unit) => ({ context: `campaignRecruitUnits/${unit.id}`, unit })), itemCatalog, itemKeys, equipmentSlots, equipmentExpToNext ); if (errors.length) { console.error(`Equipment catalog data verification failed with ${errors.length} issue(s):`); errors.slice(0, 120).forEach((error) => console.error(`- ${error}`)); if (errors.length > 120) { console.error(`- ...and ${errors.length - 120} more`); } process.exitCode = 1; } else { console.log( `Verified ${itemEntries.length} equipment catalog items and ${Object.keys(battleScenarios).length} battle equipment loadouts.` ); } } finally { await server.close(); } function validateItemCatalog(errors, itemEntries, slotKeys) { const names = new Map(); itemEntries.forEach(([itemId, item]) => { const context = `itemCatalog/${itemId}`; if (!item || typeof item !== 'object') { errors.push(`${context}: must be an item object`); return; } if (item.id !== itemId) { errors.push(`${context}: item.id "${item.id}" does not match catalog key`); } assertNonEmptyString(errors, item.name, `${context}: name`); assertNonEmptyString(errors, item.description, `${context}: description`); if (!slotKeys.has(item.slot)) { errors.push(`${context}: unknown equipment slot "${item.slot}"`); } if (!validRanks.has(item.rank)) { errors.push(`${context}: unknown rank "${item.rank}"`); } if (!Array.isArray(item.effects) || item.effects.length === 0) { errors.push(`${context}: effects must contain at least one entry`); } else { item.effects.forEach((effect, index) => { assertNonEmptyString(errors, effect, `${context}: effects[${index}]`); }); } ['attackBonus', 'defenseBonus', 'strategyBonus'].forEach((bonusKey) => { const value = item[bonusKey]; if (value !== undefined && (!Number.isInteger(value) || value < 0 || value > 20)) { errors.push(`${context}: invalid ${bonusKey} ${value}`); } }); if (typeof item.name === 'string') { const existingItemId = names.get(item.name); if (existingItemId) { errors.push(`${context}: duplicate item name "${item.name}" also used by ${existingItemId}`); } names.set(item.name, itemId); } }); } function validateUnitEquipment(errors, unitEntries, itemCatalog, itemKeys, equipmentSlots, equipmentExpToNext) { unitEntries.forEach(({ context, unit }) => { if (!unit?.equipment) { errors.push(`${context}: missing equipment`); return; } equipmentSlots.forEach((slot) => { const state = unit.equipment[slot]; if (!state) { errors.push(`${context}: missing ${slot} equipment`); return; } if (!itemKeys.has(state.itemId)) { errors.push(`${context}: unknown ${slot} item "${state.itemId}"`); } else if (itemCatalog[state.itemId].slot !== slot) { errors.push(`${context}: ${slot} item "${state.itemId}" belongs to ${itemCatalog[state.itemId].slot}`); } if (!Number.isInteger(state.level) || state.level < 1 || state.level > 9) { errors.push(`${context}: invalid ${slot} level ${state.level}`); } if (!Number.isInteger(state.exp) || state.exp < 0) { errors.push(`${context}: invalid ${slot} exp ${state.exp}`); } else if (state.level < 9 && state.exp >= equipmentExpToNext(state.level)) { errors.push(`${context}: ${slot} exp ${state.exp} should be below next level threshold ${equipmentExpToNext(state.level)}`); } }); }); } function collectBattleUnits(battleScenarios) { return Object.entries(battleScenarios).flatMap(([battleId, scenario]) => scenario.units.map((unit) => ({ context: `${battleId}/${unit.id}`, unit })) ); } function assertNonEmptyString(errors, value, context) { if (typeof value !== 'string' || value.trim().length === 0) { errors.push(`${context} must be a non-empty string`); } }