magicciv/tools/add-encyclopedia-metadata.mjs
2026-04-07 17:52:04 -07:00

181 lines
5.1 KiB
JavaScript

#!/usr/bin/env node
/**
* Adds `encyclopedia` metadata to resource JSON files.
* Reads each file, adds the block based on directory → category mapping,
* extracts relevant tags from the resource's own fields, and writes back.
*
* Usage: node tools/add-encyclopedia-metadata.mjs [--dry-run]
*/
import { readdir, readFile, writeFile } from 'node:fs/promises'
import { join, relative } from 'node:path'
const RESOURCES = 'public/resources'
const DRY_RUN = process.argv.includes('--dry-run')
/** Directory → encyclopedia category + entry_type + detail_route mapping */
const MAPPINGS = [
{
dir: 'units',
category: 'combat',
entry_type: 'unit',
detail_route: '/military/units',
tag_fields: ['domain', 'unit_type', 'attack_type'],
exclude: /\.schema\.json$/,
},
{
dir: 'buildings',
category: 'civilization',
entry_type: 'building',
detail_route: '/buildings/buildings',
tag_fields: ['category'],
exclude: /\.schema\.json$|building_categories\.json$|naval_buildings\.json$/,
filter: (obj) => !obj.wonder_type,
},
{
dir: 'buildings',
category: 'civilization',
entry_type: 'wonder',
detail_route: '/buildings/wonders',
tag_fields: ['wonder_type'],
exclude: /\.schema\.json$|building_categories\.json$|naval_buildings\.json$/,
filter: (obj) => !!obj.wonder_type,
},
{
dir: 'improvements',
category: 'civilization',
entry_type: 'improvement',
detail_route: '/buildings/improvements',
tag_fields: [],
exclude: /\.schema\.json$|registry\.md$/,
},
{
dir: 'deposits',
category: 'world',
entry_type: 'deposit',
detail_route: '/map/resources',
tag_fields: ['category'],
exclude: /\.schema\.json$|deposit_categories\.json$|registry\.md$/,
},
{
dir: 'techs',
category: 'civilization',
entry_type: 'tech',
detail_route: '/research/tech-tree',
tag_fields: ['pillar'],
exclude: /\.schema\.json$/,
},
{
dir: 'ecology/fauna/species',
category: 'world',
entry_type: 'creature',
detail_route: '/climate/ecosystem/fauna',
tag_fields: ['domain', 'trophic_level'],
exclude: /\.schema\.json$/,
},
]
function extractTags(obj, tagFields) {
const tags = []
for (const field of tagFields) {
const val = obj[field]
if (typeof val === 'string' && val) tags.push(val)
if (Array.isArray(val)) tags.push(...val.filter(v => typeof v === 'string'))
}
return tags
}
async function processFile(filePath, mapping) {
const raw = await readFile(filePath, 'utf-8')
let data
try {
data = JSON.parse(raw)
} catch {
console.warn(` SKIP (parse error): ${filePath}`)
return { skipped: true }
}
// Handle array files (buildings/economy.json has array of buildings)
if (Array.isArray(data)) {
let modified = false
for (const item of data) {
if (!item.id || !item.name) continue
if (mapping.filter && !mapping.filter(item)) continue
if (item.encyclopedia) continue // already has it
const tags = extractTags(item, mapping.tag_fields)
item.encyclopedia = {
category: mapping.category,
entry_type: mapping.entry_type,
...(mapping.detail_route ? { detail_route: mapping.detail_route } : {}),
tags,
}
modified = true
}
if (modified && !DRY_RUN) {
await writeFile(filePath, JSON.stringify(data, null, 2) + '\n')
}
return { modified, count: data.filter(i => i.encyclopedia).length }
}
// Single object file
if (!data.id || !data.name) {
return { skipped: true }
}
if (mapping.filter && !mapping.filter(data)) {
return { skipped: true }
}
if (data.encyclopedia) {
return { skipped: true, reason: 'already has encyclopedia' }
}
const tags = extractTags(data, mapping.tag_fields)
data.encyclopedia = {
category: mapping.category,
entry_type: mapping.entry_type,
...(mapping.detail_route ? { detail_route: mapping.detail_route } : {}),
tags,
}
if (!DRY_RUN) {
await writeFile(filePath, JSON.stringify(data, null, 2) + '\n')
}
return { modified: true, count: 1 }
}
async function collectFiles(dir, exclude) {
const absDir = join(RESOURCES, dir)
const entries = await readdir(absDir, { withFileTypes: true })
return entries
.filter(e => e.isFile() && e.name.endsWith('.json') && !exclude.test(e.name))
.map(e => join(absDir, e.name))
}
async function main() {
console.log(DRY_RUN ? '=== DRY RUN ===' : '=== WRITING FILES ===')
let totalModified = 0
let totalEntries = 0
for (const mapping of MAPPINGS) {
const files = await collectFiles(mapping.dir, mapping.exclude)
let dirModified = 0
let dirEntries = 0
for (const file of files) {
const result = await processFile(file, mapping)
if (result.modified) {
dirModified++
dirEntries += result.count || 1
}
}
const rel = mapping.dir
console.log(` ${rel} (${mapping.entry_type}): ${dirModified} files, ${dirEntries} entries`)
totalModified += dirModified
totalEntries += dirEntries
}
console.log(`\nTotal: ${totalModified} files modified, ${totalEntries} encyclopedia entries`)
}
main().catch(console.error)