181 lines
5.1 KiB
JavaScript
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)
|