import { readFileSync, readdirSync } from 'node:fs'; import { join } from 'node:path'; // cb 컨트롤러가 참조하는 /ui/DefaultGroup/... 경로를, 사용자가 메이커에서 재편한 // 새 UIGroup(.ui) 구조에 대조해 "그대로 옮겨지면 해결(resolved)" vs "이름/구조가 바뀌어 // 못 찾음(GAP)"을 분류. GAP = 사용자에게 구→신 매핑을 받아야 할 항목. // 출력은 경로 이름 + EXISTS/GAP boolean 뿐(엔티티 값 본문 미출력 → RULES §2 준수). // 섹션(=DefaultGroup 다음 첫 세그먼트) → 이동된 UIGroup (uimap.mjs 탐색 결과 기반) const SECTION_TO_GROUP = { MainMenu: 'DefaultGroup', Button_Attack: 'DefaultGroup', Button_Jump: 'DefaultGroup', UIJoystick: 'DefaultGroup', CharacterSelectHud: 'SelectUIGroup', JobChoiceHud: 'SelectUIGroup', JobSelectHud: 'SelectUIGroup', LobbyHud: 'LobbyUIGroup', BoardHud: 'LobbyUIGroup', SoulShopHud: 'LobbyUIGroup', CombatHud: 'RunUIGroup', DeckHud: 'RunUIGroup', CardHand: 'RunUIGroup', MapHud: 'RunUIGroup', RewardHud: 'RunUIGroup', ShopHud: 'RunUIGroup', RestHud: 'RunUIGroup', TreasureHud: 'RunUIGroup', DeckInspectHud: 'DeckUIGroup', DeckAllHud: 'DeckUIGroup', }; // 새 .ui 로드 const UI_DIR = 'ui'; const ui = {}; for (const f of readdirSync(UI_DIR).filter((x) => x.endsWith('.ui'))) { ui[f.replace('.ui', '')] = readFileSync(join(UI_DIR, f), 'utf8'); } // cb 소스에서 /ui/DefaultGroup/... 경로 리터럴 추출 (템플릿 ${...} 포함) const CB_DIR = 'tools/deck/cb'; const re = /\/ui\/DefaultGroup\/[^"'`\s),]+/g; const paths = new Set(); for (const f of readdirSync(CB_DIR).filter((x) => x.endsWith('.mjs'))) { const src = readFileSync(join(CB_DIR, f), 'utf8'); for (const m of src.match(re) || []) paths.add(m); } // 동적 세그먼트(${...}) 앞 정적 prefix만 취해 존재검사 (e.g. .../Card${i} → .../Card) function staticPrefix(p) { const i = p.indexOf('${'); if (i === -1) return { p, dyn: false }; // ${...} 직전까지 (마지막 세그먼트의 정적 앞부분 포함) return { p: p.slice(0, i), dyn: true }; } const bySection = {}; for (const p of [...paths].sort()) { const rest = p.slice('/ui/DefaultGroup/'.length); // Section/child/... const section = rest.split('/')[0].split('${')[0]; const group = SECTION_TO_GROUP[section] || '??'; const newPath = group === '??' ? p : p.replace('/ui/DefaultGroup/', `/ui/${group}/`); const { p: probe } = staticPrefix(newPath); const blob = ui[group] || ''; const exists = group !== '??' && blob.includes(probe); (bySection[section] ||= []).push({ old: p, neu: newPath, exists, group }); } // 보고 let totResolved = 0, totGap = 0; for (const section of Object.keys(bySection).sort()) { const rows = bySection[section]; const group = rows[0].group; const gaps = rows.filter((r) => !r.exists); const ok = rows.length - gaps.length; totResolved += ok; totGap += gaps.length; console.log(`\n[${section}] → ${group} 해결 ${ok} / GAP ${gaps.length} (총 ${rows.length})`); for (const g of gaps) { // GAP만 상세 출력 (사용자가 신규 이름 채워야 할 대상) console.log(` GAP ${g.old.replace('/ui/DefaultGroup/', '')} →(없음) ${g.neu.replace(`/ui/${group}/`, `${group}: `)}`); } } console.log(`\n=== 합계: 자동해결 ${totResolved} / GAP ${totGap} (distinct 경로 ${paths.size}) ===`);