/** * Gate del paso 2 (codemod D-TH.6): el diff de `generated/base.css` son * renombres 1:1 y NADA más — cero cambios de valor. * * Dos formas legítimas de aparecer en el diff, y ninguna más: * * 1. RENOMBRE la declaración cambia de nombre; su valor, salvo las * referencias que el propio plan reapunta, es idéntico. * 2. REAPUNTADA la declaración conserva su nombre (típicamente un privado * `--_c-*`) y su valor sólo cambia porque REFERENCIA un * token renombrado. * * El plan se lee del registro que el codemod escribió ANTES de tocar nada: * releer el clasificador aquí daría cero desviaciones —ya están renombradas— * y el gate pasaría sobre el vacío. */ import { execSync } from 'node:child_process'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; interface Decl { name: string; value: string; } const parse = (line: string): Decl | null => { const m = line.slice(1).match(/^\s*(--[a-z0-9_-]+)\s*:\s*(.*?);?\s*$/); return m ? { name: m[1], value: m[2] } : null; }; const diff = execSync('git diff -U0 src/uix/eidos/generated/base.css', { encoding: 'utf8', maxBuffer: 1 << 28 }).split('\n'); const removedLines = diff.filter((l) => l.startsWith('-') && !l.startsWith('---')); const addedLines = diff.filter((l) => l.startsWith('+') && !l.startsWith('+++')); const plan = new Map(); for (const r of JSON.parse(readFileSync(resolve('scripts/__names-plan.json'), 'utf8')) as Array<{ component: string; from: string; to: string; }>) plan.set(`--${r.component}-${r.from}`, `--${r.component}-${r.to}`); const ordered = [...plan].sort((a, b) => b[0].length - a[0].length); const applyPlan = (value: string) => { let out = value; for (const [from, to] of ordered) out = out.replace(new RegExp(from.replace(/-/g, '\\-') + '(?![a-zA-Z0-9-])', 'g'), to); return out; }; const removed = removedLines.map(parse); const added = addedLines.map(parse); const unparsed = [...removed, ...added].filter((d) => d === null).length; /** name → todos los valores añadidos bajo ese nombre. */ const addedByName = new Map(); for (const a of added) if (a) addedByName.set(a.name, [...(addedByName.get(a.name) ?? []), a.value]); let renamed = 0; let rewired = 0; const problems: string[] = []; for (const r of removed) { if (!r) continue; const to = plan.get(r.name); if (to) { const vals = addedByName.get(to) ?? []; if (vals.includes(r.value) || vals.includes(applyPlan(r.value))) renamed++; else problems.push( `VALOR CAMBIÓ ${r.name} → ${to}\n antes: ${r.value}\n ahora: ${vals.join(' | ') || '(ausente)'}` ); continue; } // Sin renombre: sólo vale que su valor se haya reapuntado por el plan. const vals = addedByName.get(r.name) ?? []; const rewiredValue = applyPlan(r.value); if (rewiredValue !== r.value && vals.includes(rewiredValue)) rewired++; else problems.push(`RETIRADA SIN PLAN ${r.name}: ${r.value}`); } /** Ningún nombre añadido puede ser una invención. */ const legalNames = new Set([...plan.values(), ...removed.filter(Boolean).map((d) => d!.name)]); const invented = added.filter((a) => a && !legalNames.has(a.name)) as Decl[]; console.log(`líneas retiradas ${removedLines.length} · añadidas ${addedLines.length}`); console.log(`renombres 1:1 ${renamed} / ${plan.size} planificados · reapuntadas ${rewired}`); console.log(`sin parsear ${unparsed} · añadidos inventados ${invented.length} · problemas ${problems.length}`); for (const p of problems.slice(0, 8)) console.log(' XX ' + p); for (const i of invented.slice(0, 8)) console.log(' XX INVENTADO ' + i.name); const ok = problems.length === 0 && invented.length === 0 && unparsed === 0 && renamed === plan.size; console.log(ok ? '\nGATE VERDE: renombres 1:1, cero cambios de valor' : '\nGATE ROJO'); process.exit(ok ? 0 : 1);